2022NOIP联测7 10月11日
2022NOIP联测7 10月11日
找
题解做法:
欧几里得距离公式:
直接预处理 \(\sum x_i^2 \sum y_i^2 \sum x_i \sum y_i \ O(1)\) 查询。
我的做法:
将 \(x\) 轴与 \(y\) 轴分开处理,两个互不影响,最后答案加起来。
先从小到大排序,处理 \(x_1\) 的解,\(O(1)\) 转移下一个的答案,也是拆的平方差的式子,但有些麻烦。
Code moo~~
// 好像可以 O(1) 转移
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define re register int
#define pc_ putchar(' ')
#define pc_n putchar('\n')
#define Bessie int
const int MOD = 998244353, CTR = 2e5 + 7;
int n;
ll ansx[CTR], ansy[CTR], sumx[CTR], sumy[CTR], sumpre, sumend;
struct star
{
ll x, y;
int idx;
}a[CTR];
bool cmp1(star A, star B)
{
return A.x < B.x;
}
bool cmp2(star A, star B)
{
return A.y < B.y;
}
Bessie main()
{
n = read();
for(re i = 1; i <= n; ++i)
{
a[i].x = read(), a[i].y = read(), a[i].idx = i;
}
sort(a + 1, a + n + 1, cmp1);
for(re i = 1; i < n; ++i)
{
sumx[i] = a[i + 1].x - a[i].x;
sumx[i] += sumx[i - 1];
sumend = (sumend + sumx[i]) % MOD;
}
for(re i = 1; i < n; ++i)
{
ansx[a[1].idx] = (ansx[a[1].idx] + sumx[i] * sumx[i] % MOD) % MOD;
}
sumpre = 0;
// printf("pre:%lld end:%lld\n", sumpre, sumend);
for(re i = 2; i <= n; ++i)
{
sumend = (sumend - (sumx[i - 1] - sumx[i - 2]) * (n - i + 1) % MOD) % MOD;
ansx[a[i].idx] = (
ansx[a[i - 1].idx]
+ (i - 2 - (n - i)) * (sumx[i - 1] - sumx[i - 2]) % MOD * (sumx[i - 1] - sumx[i - 2]) % MOD
+ 2 * (sumx[i - 1] - sumx[i - 2]) % MOD * sumpre % MOD
- 2 * (sumx[i - 1] - sumx[i - 2]) % MOD * sumend % MOD
);
sumpre = (sumpre + (sumx[i - 1] - sumx[i - 2]) * (i - 1) % MOD) % MOD;
ansx[a[i].idx] = (ansx[a[i].idx] % MOD + MOD) % MOD;
// printf("pre:%lld end:%lld\n", sumpre, sumend);
}
sumend = 0;
sort(a + 1, a + n + 1, cmp2);
for(re i = 1; i < n; ++i)
{
sumy[i] = a[i + 1].y - a[i].y;
sumy[i] += sumy[i - 1];
sumend = (sumend + sumy[i]) % MOD;
}
for(re i = 1; i < n; ++i)
{
ansy[a[1].idx] = (ansy[a[1].idx] + sumy[i] * sumy[i] % MOD) % MOD;
}
sumpre = 0;
// printf("pre:%lld end:%lld\n", sumpre, sumend);
for(re i = 2; i <= n; ++i)
{
sumend = (sumend - (sumy[i - 1] - sumy[i - 2]) * (n - i + 1) % MOD) % MOD;
ansy[a[i].idx] = (
ansy[a[i - 1].idx]
+ (i - 2 - (n - i)) * (sumy[i - 1] - sumy[i - 2]) % MOD * (sumy[i - 1] - sumy[i - 2]) % MOD
+ 2 * (sumy[i - 1] - sumy[i - 2]) % MOD * sumpre % MOD
- 2 * (sumy[i - 1] - sumy[i - 2]) % MOD * sumend % MOD
);
sumpre = (sumpre + (sumy[i - 1] - sumy[i - 2]) * (i - 1) % MOD) % MOD;
ansy[a[i].idx] = (ansy[a[i].idx] % MOD + MOD) % MOD;
// printf("pre:%lld end:%lld\n", sumpre, sumend);
}
for(re i = 1; i <= n; ++i) {
printf("%lld\n", (ansx[i] + ansy[i]) % MOD);
}
return 0;
}
女
题解(树剖)
考虑树上两点间的距离为 \(dis(u,v)=dep_u+dep_v-2dep_{lca}\) ,想到对于一个询问 \(u\) 来说,要找到最小的 \(dep_v-2dep_{lca}\) ,\(v\) 是标记的黑点。
先树剖一下建线段树,每次修改时向上更新节点维护的 \(dep_v-2dep_{fa}\) ,询问时也向上取最小值,直到根节点。
为什么是对的呢?
考虑所有修改与询问都向上跳时,他们最早一定在 \(lca\) 处相遇,由上面的距离式子可知此时的答案一定是最小的,即使跳过了,由于我们一直取 \(min\) ,也不会影响答案。
现在重点说一下线段树怎么维护。
维护两个值:\(minn \to -2dep_{fa}\) 和 \(sum \to dep_v-2dep_{fa}\) ,一个标记(子树中所有黑点的深度) \(multiset \to S\) 。标记永久化。
建树:
\(minn_{p}=-2dep_{rk_l}\) 。
修改:(设修改点为 \(k\))
1.白点变黑:
\(S\) 中加入 \(dep_k\) ,\(sum=min(sum,minn+dep_k)\) 。
2.黑点变白:
在 \(S\) 中删 \(dep_k\) ,将 \(sum\) 重设为左右儿子 \(sum\) 的较小值(如果是叶子就设为 \(\text{INF}\)),再看一下子树中有没有黑点,\(sum\) 再更新一下取较小值。
\(\text{push\_up}\) 时不仅需要左右儿子的 \(sum\) ,还要考虑他们的标记和当前节点的标记。
询问:
返回 \(pair(sum,minn)\) ,因为标记永久化,每一次向上传时也要根据当前节点的标记再次计算答案。
每次在 multiset 中询问时先要看看是否为空!
无了。
对了,树剖别像我似的打错,指建树时不用 \(dfn\) ,要用 \(rk\) 。
Code moo~~
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define re register int
#define pc_ putchar(' ')
#define pc_n putchar('\n')
#define Bessie int
const int CTR = 1e5 + 7, INF = 0x7f7f7f7f;
int n, Q;
bool black[CTR];
int blacknum;
struct edge
{
int to, nxt;
}e[CTR << 1];
int h[CTR], etot;
void addedge(int x, int y)
{
e[++etot].to = y;
e[etot].nxt = h[x];
h[x] = etot;
}
int siz[CTR], dep[CTR], fa[CTR], son[CTR];
void dfs1(int x, int f)
{
fa[x] = f;
dep[x] = dep[f] + 1;
siz[x] = 1;
for(re i = h[x], v; i; i = e[i].nxt)
{
v = e[i].to;
if(v == f) continue;
dfs1(v, x);
siz[x] += siz[v];
if(siz[v] > siz[son[x]]) son[x] = v;
}
}
int top[CTR], dfn[CTR], rk[CTR], tme;
void dfs2(int x, int topf)
{
top[x] = topf;
dfn[x] = ++tme;
rk[tme] = x;
if(son[x]) dfs2(son[x], topf);
for(re i = h[x], v; i; i = e[i].nxt)
{
v = e[i].to;
if(v == fa[x] || v == son[x]) continue;
dfs2(v, v);
}
}
typedef pair<int, int> pii;
struct Tree
{
int minn[CTR << 2], sum[CTR << 2];
multiset<int> S[CTR << 2];
#define ls(p) (p << 1)
#define rs(p) (p << 1 | 1)
#define mid ((l + r) >> 1)
void built(int p, int l, int r)
{
sum[p] = INF;
if(l == r)
{
minn[p] = -2 * dep[rk[l]];
return ;
}
built(ls(p), l, mid), built(rs(p), mid + 1, r);
minn[p] = min(minn[ls(p)], minn[rs(p)]);
}
void update(int p, int l, int r, int L, int R, int k)
{
if(L <= l && r <= R)
{
if(black[k])
{
sum[p] = min(sum[p], minn[p] + dep[k]);
S[p].insert(dep[k]);
}
else
{
S[p].erase(S[p].find(dep[k]));
if(l != r)
{
sum[p] = min(sum[ls(p)], sum[rs(p)]);
}
else
{
sum[p] = INF;
}
if(!S[p].empty())
{
sum[p] = min(sum[p], minn[p] + *S[p].begin());
}
}
return;
}
if(L <= mid) update(ls(p), l, mid, L, R, k);
if(R > mid) update(rs(p), mid + 1, r, L, R, k);
int res = INF;
if(!S[p].empty()) res = minn[p] + *S[p].begin();
sum[p] = min({sum[ls(p)], sum[rs(p)], res});
}
pii query(int p, int l, int r, int L, int R)
{
if(L <= l && r <= R)
{
return make_pair(sum[p], minn[p]);
}
pii A = { INF, INF }, B = { INF, INF };
int ans1 = INF, ans2 = INF, ans3 = INF, ans4 = INF;
if(L <= mid) A = query(ls(p), l, mid, L, R);
if(R > mid) B = query(rs(p), mid + 1, r, L, R);
ans1 = A.first, ans2 = B.first;
if(!S[p].empty()) ans3 = A.second + *S[p].begin(), ans4 = B.second + *S[p].begin();
return make_pair(min({ans1, ans2, ans3, ans4}), min(A.second, B.second));
}
}T;
void modify(int x, int y, int k)
{
if(dep[x] < dep[y]) swap(x, y);
while(top[x] != top[y])
{
T.update(1, 1, n, dfn[top[x]], dfn[x], k);
x = fa[top[x]];
}
if(dep[x] > dep[y]) swap(x, y);
T.update(1, 1, n, dfn[x], dfn[y], k);
}
int qmin(int x, int y)
{
int ans = INF;
if(dep[x] < dep[y]) swap(x, y);
while(top[x] != top[y])
{
ans = min(ans, T.query(1, 1, n, dfn[top[x]], dfn[x]).first);
x = fa[top[x]];
}
if(dep[x] > dep[y]) swap(x, y);
ans = min(ans, T.query(1, 1, n, dfn[x], dfn[y]).first);
return ans;
}
Bessie main()
{
n = read(), Q = read();
for(re i = 1, x, y; i < n; ++i)
{
x = read(), y = read();
addedge(x, y);
addedge(y, x);
}
dfs1(1, 1);
dfs2(1, 1);
T.built(1, 1, n);
int op, x;
while(Q--)
{
op = read(), x = read();
if(op == 1)
{
black[x] ^= 1;
modify(1, x, x);
if(black[x]) ++blacknum;
else --blacknum;
}
else
{
if(!blacknum) ot(-1),pc_n;
else ot(dep[x] + qmin(1, x)),pc_n;
}
}
return 0;
}
朋
科技题。还不会,写题目名称仅供一乐。
友
题解(树形背包+剪枝)
理论复杂度 \(O(nm^2)\) \(\text{TLEcoders}\) 数据水给过了。
设 \(dp_{i,j}\) 为以 \(i\) 为根的子树选了 \(a\) 的容量为 \(j\) 时的 \(b\) 的最大值。
转移平凡。
一个剪枝就是要保证 \(a\) (容量)合法,不合法直接 \(continue\) ;
上下界剪枝就是不超过子树中 \(a\) 之和。
Code moo~~
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define re register int
#define pc_ putchar(' ')
#define pc_n putchar('\n')
#define Bessie int
const int CTR = 1e3 + 7;
int n, m;
int a[CTR], b[CTR];
int atot[CTR];
struct edge
{
int to, nxt;
}e[CTR * CTR];
int h[CTR], etot;
void addedge(int x, int y)
{
e[++etot].to = y;
e[etot].nxt = h[x];
h[x] = etot;
}
int ans;
int dp[CTR][CTR * 10];
void dfs1(int x, int f)
{
dp[x][a[x]] = max(dp[x][a[x]], b[x]);
atot[x] += a[x];
for(re i = h[x], v; i; i = e[i].nxt)
{
v = e[i].to;
if(v == f) continue;
dfs1(v, x);
for(re j = min(atot[x], m); j >= 0; --j)
{
if(dp[x][j] == -1) continue;
for(re k = min(atot[v], m - j); k >= 0; --k)
{
if(dp[v][k] == -1) continue;
dp[x][j + k] = max(dp[v][k] + dp[x][j], dp[x][j + k]);
ans = max(ans, dp[x][j + k]);
}
}
atot[x] += atot[v];
}
}
Bessie main()
{
memset(dp, -1, sizeof(dp));
n = read(), m = read();
for(re i = 1; i <= n; ++i)
{
a[i] = read(), b[i] = read();
}
for(re i = 1, x, y; i < n; ++i)
{
x = read(), y = read();
addedge(x, y);
addedge(y, x);
}
dfs1(1, 1);
ot(ans);
return 0;
}
总结:T1 想的太慢,思路不好打,T2 $ \text{BFS}$ 用的 STL::queue ,每次清空是 \(O(n)\) 的没有手写快,T3 暴力没来得及打,T4 还可以。
今个三道题一个正解都没打。

浙公网安备 33010602011771号