题解 P1600 [NOIP2016 提高组] 天天爱跑步
题意简述
给定一棵 \(n\) 个节点的树,第 \(i\) 个点有一个权值 \(w_i\) ,有 \(m\) 次操作,每次操作让 \((u,v)\) 路径上的满足 \(\operatorname{dist}(u,x)=w_x\) (\(\operatorname{dist}(x,y)\) 表示 \(x\) 到 \(y\) 的距离)的 \(x\) 的答案 \(+1\) ,最后输出所有点的答案。
\(1 \leq n,m \leq 3\times 10^5 ,0 \leq w_i\leq n\) 。
Solution
先考虑如果是每一次操作求有多少个满足 \(\operatorname{dist}(u,x)=w_x\) 的点有多少个的话要怎么做。
设 \(u,v\) 的 \(lca\) 是 \(p\) ,那么对于 \((u,p)\)这一段路径上的点 \(x\) 如果满足条件就相当于(下面称这种情况为上行):
对于 \((p,v)\) 这一段路径上的点(不包括 \(p\))点 \(x\) 如果满足条件就相当于(下面称这种情况为下行):
问题就转化成了询问路径上权值等于一个数的点的个数,可以用主席树。
当然主席树是大材小用,可以直接树上差分,把 \((u,v)\) 的询问拆成 \((1,u)-(1,p)\) 的上行的询问和 \((1,v)-(1,fa_p)\) 的下行的询问(\(-\) 这里代表答案减去,钦定 \(1\) 为根节点),然后最后开一个桶, \(dfs\) 累加答案即可。
回到题目,对于每个点单独询问怎么做。
还是考虑树上差分,类似于上面的拆的方式,我们可以发现,只有 \(u\) 的子树里面的操作对 \(u\) 的答案有影响。
那么可以把上面的把点权插入到桶,遇到操作查询 换成 把操作插入到桶,\(dfs\) 到点的时候查询。当然,这里要先 \(dfs\) 子树再查询当前点的答案。
问题就解决了,复杂度 \(O(n)\)。
代码如下:
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <iostream>
#include <queue>
using namespace std;
inline int read() {
int num = 0 ,f = 1; char c = getchar();
while (!isdigit(c)) f = c == '-' ? -1 : f ,c = getchar();
while (isdigit(c)) num = (num << 1) + (num << 3) + (c ^ 48) ,c = getchar();
return num * f;
}
const int N = 3e5 + 5 ,M = 6e5 + 5;
struct Edge {
int to ,next;
Edge (int to = 0 ,int next = 0) : to(to) ,next(next) {}
}G[M]; int head[N] ,idx;
inline void add(int u ,int v) {
G[++idx] = Edge(v ,head[u]); head[u] = idx;
G[++idx] = Edge(u ,head[v]); head[v] = idx;
}
int fa[N] ,dep[N] ,siz[N] ,son[N] ,top[N];
inline void dfs1(int now) {
siz[now] = 1;
dep[now] = dep[fa[now]] + 1;
for (int i = head[now]; i ; i = G[i].next) {
int v = G[i].to;
if (v == fa[now]) continue;
fa[v] = now;
dfs1(v);
siz[now] += siz[v];
if (siz[v] > siz[son[now]]) son[now] = v;
}
}
inline void dfs2(int now ,int t) {
top[now] = t;
if (!son[now]) return ;
dfs2(son[now] ,t);
for (int i = head[now]; i ; i = G[i].next) {
int v = G[i].to;
if (v == fa[now] || v == son[now]) continue;
dfs2(v ,v);
}
}
inline int LCA(int x ,int y) {
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x ,y);
x = fa[top[x]];
}
return dep[x] < dep[y] ? x : y;
}
struct Query {
int sign ,val;
Query(int sign = 0 ,int val = 0) : sign(sign) ,val(val) {}
};
vector <Query> q1[N] ,q2[N];
int ans[N] ,t1[M] ,t2[M] ,w[N] ,n ,m;
inline void dfs(int now) {
int lasta = t1[dep[now] + w[now]] ,lastb = t2[w[now] - dep[now] + n];
for (int i = 0; i < (int)q1[now].size(); i++) {
int sign = q1[now][i].sign ,val = q1[now][i].val;
t1[val] += sign;
}
for (int i = 0; i < (int)q2[now].size(); i++) {
int sign = q2[now][i].sign ,val = q2[now][i].val;
t2[val] += sign;
}
for (int i = head[now]; i ; i = G[i].next) {
int v = G[i].to;
if (v == fa[now]) continue;
dfs(v);
}
ans[now] = t1[dep[now] + w[now]] - lasta + t2[w[now] - dep[now] + n] - lastb;
}
signed main() {
n = read() ,m = read();
for (int i = 1; i <= n - 1; i++) {
int u = read() ,v = read();
add(u ,v);
}
for (int i = 1; i <= n; i++) w[i] = read();
dfs1(1);
dfs2(1 ,1);
for (int i = 1; i <= m; i++) {
int u = read() ,v = read();
int Lca = LCA(u ,v);
q1[u].push_back(Query(1 ,dep[u]));
q1[Lca].push_back(Query(-1 ,dep[u]));
q2[v].push_back(Query(1 ,dep[u] - 2 * dep[Lca] + n));
q2[fa[Lca]].push_back(Query(-1 ,dep[u] - 2 * dep[Lca] + n));
}
dfs(1);
for (int i = 1; i <= n; i++) printf("%d%c" ,ans[i] ," \n"[i == n]);
return 0;
}