题解 P1600 [NOIP2016 提高组] 天天爱跑步_git 题解 P1600 [NOIP2016 提高组] 天天爱跑步

题意简述

Link

给定一棵 \(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\) 如果满足条件就相当于(下面称这种情况为上行):

\[dep_x-dep_u=w_x \\ dep_u=w_x+dep_x \]

对于 \((p,v)\) 这一段路径上的点(不包括 \(p\))点 \(x\) 如果满足条件就相当于(下面称这种情况为下行):

\[dep_x+dep_u-2dep_{lca}=w_x \\ dep_u-2dep_{lca}=w_x-dep_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;
}