1. 程式人生 > 其它 >題解 P1600 [NOIP2016 提高組] 天天愛跑步

題解 P1600 [NOIP2016 提高組] 天天愛跑步

題解 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;
}