初涉DSU on tree
早先以为莫队是个顶有用的东西,不过好像树上莫队(不带修)被dsu碾压?
dsu one tree起源
dsu on tree是有人在cf上blog上首发的一种基于轻重链剖分的算法,然后好像由因为这个人后来在cf上办了场比赛出了道dsu on tree的裸题由此出名?
这个是原博客地址:http://codeforces.com/blog/entry/44351
大概思想就是一种树上启发式合并,利用轻重链剖分把重复计算的答案利用起来,从而把时间复杂度控制在$O(n log n)$(不过不能修改)。
注意dsu的一大特点:一次dsu之后是把整棵树的答案都处理出来,因此它更适合大量查询的情况。
算法流程
下面讲一下算法流程:
1.预处理树的结构,把$fa$,$son$,$tot$处理出来。具体操作参见树剖。
2.为了统计当前节点答案,先递归处理所有轻儿子。然后递归回当前节点时,其子树内除了重儿子都已经处理好答案了。
3.如果有重儿子,那么递归重儿子同时标记重儿子这个节点
4.现在子树信息都已经处理好了,考虑向上合并信息。我们把子树内所有点都统计颜色一遍(除了重儿子的家族)
5.现在子树信息传递好了
6.如果这个节点是轻儿子转移过来的,那么清除这颗子树所有信息(包括子树里的重儿子)
7.向上递归返回
1 void color(int x, int c) //把x的子树都统计一遍 2 { 3 cnt[a[x].col] += c; 4 if (c>0&&mx <= cnt[a[x].col]) 5 { 6 if (mx < cnt[a[x].col]) mx = cnt[a[x].col], sum = 0; 7 sum += a[x].col; //如果现在是 8 } 9 for (int i=0; i<f[x].size(); i++) 10 if (f[x][i]!=a[x].fa&&!vis[f[x][i]]) //处理儿子节点 11 color(f[x][i], c); 12 } 13 void dfs2(int x, bool fl) //dsu:x表示当前节点 fl表示当前节点是轻儿子还是重儿子 14 { 15 for (int i=0; i<f[x].size(); i++) 16 if (f[x][i]!=a[x].son&&f[x][i]!=a[x].fa) 17 dfs2(f[x][i], 0); 18 if (a[x].son!=-1) dfs2(a[x].son, 1), vis[a[x].son] = 1; 19 color(x, 1); //c==1是统计答案;c==0是消除答案 20 ans[x] = sum; 21 if (a[x].son!=-1) vis[a[x].son] = 0; 22 if (!fl) color(x, -1), mx = sum = 0; 23 }
上面讲得似乎不适合初学者?……学的时候也看这些文字描述,不过还是有些疑惑。下面用一些图片来描述一下。
简单易懂的图片描述?
假设现在我们已经递归处理好了重儿子和轻儿子,现在打算向上传递到x节点。
我们的重儿子的颜色的信息是保存下来的,所以只需要再次添加轻儿子的颜色信息。
光光这样是不够的,可以想到如果出现这种情况呢?
现在x节点的颜色信息已经被确定了,并且在a统计时候信息并没有删除,好像这样会对b节点统计时候产生影响???
如果你这样想那么就跟我一样了。
注意到$dsu$的$dfs2$时候带了个$fl$参数表示当前节点是轻儿子还是重儿子。那么这个东西到底是干嘛用的呢?
这个a节点是由轻儿子转移过来的,因此它的$fl==0$。然而我们知道轻儿子是不保存信息的。
当a节点向上递归回去的时候,说明a节点的子树都已经统计好了。那么最后就有一个$color(x, -1)$操作,把x子树全部「清洗」掉去。
这样子按照轻重链剖分的顺序我们就能够利用大量重复的子树颜色信息,从而控制复杂度在$O(n log n)$了!
FAQ
这是是来自dalao的疑问:
x_o_r:上面那个算法流程第六步在干吗?
aq:这个是算法流程。
x_o_r:为什么要这么做?
aq:这个是流程,你首先假装它是对的。现在你哪里没看懂?
x_o_r:这个算法如何证明复杂度和正确性?
(x_o_r掉线)
XSamsara:为什么流程要先处理轻儿子再处理重儿子?不能反过来吗?
(x_o_r:轻重链剖分不就是暴力吗)
aq:因为这样快啊 这里为了统计这个节点,我们需要开一个桶。呃我知道这个“桶”的概念很抽象,就是理解成为一个颜色的hash数组,它的作用就是记录什么颜色有多少个。举个例子hash[x]=c就是表示x这个颜色有c个。诶如果想看复杂度证明的话,cptraser的博客写了个证明的。
XSamsara:轻重链有什么区别?为什么要区分当前节点是轻儿子还是重儿子?
(片刻)
XSamsara:哦,如果当前节点是轻儿子,最后擦除它是不是为了不影响其他子树的统计?但是为什么还要区分轻重链?是为了正确性还是为了效率?
aq:剖分轻重链的目的就是为了快。比方说我们先从重儿子开始做也可以啊。但是按照轻重儿子的顺序就能够把复杂度控制在$O(nlogn)$。
XSamsara:按照轻重儿子的顺序又会怎么样呢?为什么能够节省复杂度?
aq:考虑我们最最暴力的$O(n^2)$操作,统计完之后为了不影响其他子树统计,我们每一次都需要擦除整颗子树在桶里面的信息。这里,我们先做轻儿子并擦除,最后来统计重儿子,于是我们重儿子的信息就可以先不用擦了,因为当前节点也需要重儿子整颗子树的信息啊!
XSanasara:噢……原来轻重链剖分只是为了效率,我对它的期望太高了,以为是什么玄妙操作……
后续应用
1.dfs序不修改莫队的优秀替代品:$O(\sqrt n) -> O(log n)$
2.然后结合点分治可做一些有根树上的路径统计问题
似乎讲的很清晰易懂啊?
题目
Educational Codeforces Round 2 E Lomsat gelral
You are given a rooted tree with root in vertex 1. Each vertex is coloured in some colour.
Let's call colour c dominating in the subtree of vertex v if there are no other colours that appear in the subtree of vertex v more times than colour c. So it's possible that two or more colours will be dominating in the subtree of some vertex.
The subtree of vertex v is the vertex v and all other vertices that contains vertex v in each path to the root.
For each vertex v find the sum of all dominating colours in the subtree of vertex v.
Input
The first line contains integer n (1 ≤ n ≤ 105) — the number of vertices in the tree.
The second line contains n integers ci (1 ≤ ci ≤ n), ci — the colour of the i-th vertex.
Each of the next n - 1 lines contains two integers xj, yj (1 ≤ xj, yj ≤ n) — the edge of the tree. The first vertex is the root of the tree.
Output
Print n integers — the sums of dominating colours for each vertex.
题目大意
就是询问所有子树中出现次数最多的颜色和。
题目分析
上面应该讲的很清楚了。
1 #include<bits/stdc++.h> 2 typedef long long ll; 3 const int maxn = 100035; 4 5 struct node 6 { 7 int fa,tot,son; 8 ll col; 9 }a[maxn]; 10 std::vector<int> f[maxn<<1]; 11 bool vis[maxn]; 12 int n; 13 ll cnt[maxn],ans[maxn],mx,sum; 14 15 void dfs1(int x, int fa) 16 { 17 a[x].fa = fa, a[x].son = -1, a[x].tot = 1; 18 for (int i=0; i<f[x].size(); i++) 19 if (f[x][i]!=fa){ 20 dfs1(f[x][i], x); 21 a[x].tot += a[f[x][i]].tot; 22 if (a[x].son ==-1||a[a[x].son].tot < a[f[x][i]].tot) 23 a[x].son = f[x][i]; 24 } 25 } 26 void color(int x, int c) 27 { 28 cnt[a[x].col] += c; 29 if (c>0&&mx <= cnt[a[x].col]) 30 { 31 if (mx < cnt[a[x].col]) mx = cnt[a[x].col], sum = 0; 32 sum += a[x].col; 33 } 34 for (int i=0; i<f[x].size(); i++) 35 if (f[x][i]!=a[x].fa&&!vis[f[x][i]]) 36 color(f[x][i], c); 37 } 38 void dfs2(int x, bool fl) 39 { 40 for (int i=0; i<f[x].size(); i++) 41 if (f[x][i]!=a[x].son&&f[x][i]!=a[x].fa) 42 dfs2(f[x][i], 0); 43 if (a[x].son!=-1) dfs2(a[x].son, 1), vis[a[x].son] = 1; 44 color(x, 1); 45 ans[x] = sum; 46 if (a[x].son!=-1) vis[a[x].son] = 0; 47 if (!fl) color(x, -1), mx = sum = 0; 48 } 49 int main() 50 { 51 scanf("%d",&n); 52 for (int i=1; i<=n; i++) scanf("%I64d",&a[i].col); 53 for (int i=1; i<n; i++) 54 { 55 int x,y; 56 scanf("%d%d",&x,&y); 57 f[x].push_back(y), f[y].push_back(x); 58 } 59 dfs1(1, 0); 60 dfs2(1, 0); 61 for (int i=1; i<=n; i++) printf("%I64d ",ans[i]); 62 return 0; 63 }
(注意一下要开long long)
END