题意:给定一些树上的路径覆盖,求出每个点被覆盖了多少次。
拿到这题第一反应显然是树剖。。但实际上有更加方便的做法,那就是树上差分。
首先我们联想一下对数列进行差分的做法。序列差分这个技巧一般适用于:执行若干次区间加减,到最后再统计每个点的权值。设T是A的差分序列,我们把将a~b这个区间中的每个点加上c的操作(A[i]+=c a<=i<=b)转变成对T的操作T[a]+=c,T[b+1]-=c,最后A[k]的值就是Sum{T[i]}(1<=i<=k)即T的一个前缀和。这个很容易能理解,随便在脑中yy一下或者画画图都能理解。
然后我们再来看下这道题。这道题与序列上有一定相似之处:执行若干次路径上的加减,最后统计每个点的权值。我们可以将在序列上进行差分的方法拓展到树上。对于s~t这个路径上每个点加上c的操作,我们可以对树A的“差分树”T进行操作:T[s]+=c,T[t]+=c,T[LCA(s,t)]-=c,T[Father[LCA(s,t)]]-=c。即给s这个点加上c,给t这个店加上c,给s和t的最近公共祖先和这个祖先的父亲减去c。最后每个点的权值就是这个点的子树权值之和。
这样做为什么是正确的呢?我们来yy一下一般情况:对于一棵树,从s走到t可以这么走:先从s走到LCA(s,t),再从t走到LCA(s,t)。为了更方便接下来的叙述,我们可以想象有两个人分别从s和t走到LCA(s,t)。然后,我们要将路径s~t上每个点加上c,因为我们最后统计的是每个点的子树权值之和,所以说白了就是这个标记要向它的父亲上传;所以当我们给s和t都加上c后,上传到LCA(s,t)的时候就变成了2c,显然在这里是要减去c的;而对于LCA(s,t)的父亲,由于LCA(s,t)的标记也要上传,所以在LCA(s,t)的父亲处再减去一个c。这段话比较长,但是只要深刻理解数列差分的技巧,理解树上差分也是很容易的。
最后说几个实现上的细节。
第一,对于LCA(s,t),如果它是根,那么就没有将它父亲减去c的操作了。
第二,最后子树统计的时候的方法比较多。可以用类似拓扑排序的思想,从最后一层结点“逐层上传”。也可以用树型dp。这个随个人喜好而定。
具体看代码~
#include <vector> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const size_t Max_N(300050); const size_t Max_M(600050); const int root(1); void Get_Val(int &Ret) { Ret = 0; char ch; while ((ch = getchar()), (ch > '9' || ch < '0')) ; do { (Ret *= 10) += ch - '0'; } while ((ch = getchar()), (ch >= '0' && ch <= '9')); } int N; int A[Max_N]; int Father[Max_N], Deep[Max_N]; vector<int> Son[Max_N]; int Anc[Max_N][35]; int Total; int Head[Max_N]; int To[Max_M], Next[Max_M]; int Value[Max_N]; inline void Add_Edge(const int &s, const int &t) { ++Total; To[Total] = t; Next[Total] = Head[s], Head[s] = Total; } void init() { int x, y; Get_Val(N); for (int i = 1;i <= N;++i) Get_Val(A[i]); for (int i = 1;i != N;++i) { Get_Val(x), Get_Val(y); Add_Edge(x, y), Add_Edge(y, x); } } void make_tree(const int &u, const int &fa) { int v; for (int i = Head[u];i;i = Next[i]) { v = To[i]; if (v != fa) { Deep[v] = Deep[u] + 1; Son[u].push_back(v); Father[v] = u; make_tree(v, u); } } } void preprocess() { memset(Anc, -1, sizeof(Anc)); for (int i = 1;i <= N;++i) Anc[i][0] = Father[i]; for (int j = 1;(1 << j) <= N;++j) for (int i = 1;i <= N;++i) Anc[i][j] = Anc[Anc[i][j - 1]][j - 1]; } int LCA(int p, int q) { if (Deep[p] < Deep[q]) swap(p, q); int log; for (log = 1;(1 << log) <= Deep[p];++log); --log; for (int i = log;i >= 0;--i) if (Deep[p] - (1 << i) >= Deep[q]) p = Anc[p][i]; if (p == q) return p; for (int i = log;i >= 0;--i) if (Anc[p][i] != -1 && Anc[p][i] != Anc[q][i]) p = Anc[p][i], q = Anc[q][i]; return Father[p]; } void Add(const int &s, const int &t, const int &w) { int lca = LCA(s, t); Value[s] += w, Value[t] += w, Value[lca] -= w; if (lca != root) Value[Father[lca]] -= w; } void SonTree_Sum(const int &u) { for (vector<int>::size_type i = 0;i != Son[u].size();++i) { SonTree_Sum(Son[u][i]); Value[u] += Value[Son[u][i]]; } } void work() { Deep[root] = 1, make_tree(root, Father[root] = -1); preprocess(); for (int i = 2;i <= N;++i) { Add(A[i - 1], A[i], 1); Add(A[i], A[i], -1); } SonTree_Sum(root); for (int i = 1;i <= N;++i) printf("%d\n", Value[i]); } int main() { init(); work(); return 0; }