[CF1009F] Dominant Indices (+dsu on tree详解)
这道题用到了dsu(Disjoint Set Union) on tree,树上启发式合并。
先看了CF的官方英文题解,又看了看zwz大佬的题解,差不多理解了dsu on tree的算法。
但是时间复杂度有点玄学,问了一下zwz大佬才懂了为什么是nlogn。
先考虑暴力n^2的算法。
显然对于某个点,搜一遍它的子树,就能得到这个点的答案。这一步是O(n)的。
每个点都这么搞一遍,就是O(n^2)的暴力做法。
但是这个暴力做法有一点不足,子节点的答案没有应用到父节点的计算中,白白浪费时间重算一遍。
考虑优化,类似树链剖分,找出子树最大的儿子称作重儿子,把它的答案留着,这样计算父节点时就不用搜这个重儿子了。
显然保留子树最大的儿子的信息,能够节约最多的时间。
但是如果计算完重儿子的答案,保留了信息,再计算别的儿子的答案,已保留的信息会对当前的计算产生干扰。
所以我们先计算轻儿子,最后计算重儿子。
如果是轻儿子,更新答案计算后,暴力再改回去。
如果是重儿子,就留着。
计算完所有儿子的答案后,最后计算当前点。
只需要加上轻儿子的信息就好。重儿子的信息已经留着了,不用再加了。
下面是zwz大佬对于dsu on tree时间复杂度的证明:
每个节点只会在祖先节点的计算中被搜到。
而且只有它到它父亲是轻边的时候才会搜一遍。
所以每个点的计算次数是它到根的轻边数量,为logn。
所以总时间复杂度是nlogn。
感觉dsu也是挺暴力的,每次留一个,居然时间上优化了很多。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<queue> 5 #define MAXN 1000005 6 using namespace std; 7 8 int n; 9 int hd[MAXN],nx[MAXN<<1],to[MAXN<<1],ec; 10 int dep[MAXN],sz[MAXN],ans[MAXN],cnt[MAXN]; 11 12 void edge(int af,int at) 13 { 14 to[++ec]=at; 15 nx[ec]=hd[af]; 16 hd[af]=ec; 17 } 18 19 void pre(int p,int fa) 20 { 21 sz[p]=1; 22 dep[p]=dep[fa]+1; 23 for(int i=hd[p];i;i=nx[i]) 24 { 25 if(to[i]==fa)continue; 26 pre(to[i],p); 27 sz[p]+=sz[to[i]]; 28 } 29 } 30 31 struct data 32 { 33 int d,v; 34 friend bool operator<(data q,data w) 35 { 36 if(q.v==w.v)return q.d>w.d; 37 return q.v<w.v; 38 } 39 }; 40 41 priority_queue<data>qq; 42 43 void add(int p,int fa) 44 { 45 cnt[dep[p]]++; 46 data neo={dep[p],cnt[dep[p]]}; 47 qq.push(neo); 48 for(int i=hd[p];i;i=nx[i])if(to[i]!=fa)add(to[i],p); 49 } 50 51 void del(int p,int fa) 52 { 53 cnt[dep[p]]--; 54 for(int i=hd[p];i;i=nx[i])if(to[i]!=fa)del(to[i],p); 55 } 56 57 void dfs(int p,int fa,int stay) 58 { 59 int son=0,mx=-1; 60 for(int i=hd[p];i;i=nx[i]) 61 { 62 if(to[i]==fa)continue; 63 if(mx<sz[to[i]])mx=sz[to[i]],son=to[i]; 64 } 65 for(int i=hd[p];i;i=nx[i]) 66 { 67 if(to[i]==fa||to[i]==son)continue; 68 dfs(to[i],p,0); 69 } 70 if(son)dfs(son,p,1); 71 for(int i=hd[p];i;i=nx[i]) 72 { 73 if(to[i]==fa||to[i]==son)continue; 74 add(to[i],p); 75 } 76 cnt[dep[p]]++; 77 data neo={dep[p],cnt[dep[p]]}; 78 qq.push(neo); 79 ans[p]=qq.top().d-dep[p]; 80 if(!stay) 81 { 82 del(p,fa); 83 while(!qq.empty())qq.pop(); 84 } 85 } 86 87 int main() 88 { 89 scanf("%d",&n); 90 for(int i=1;i<n;i++) 91 { 92 int x,y; 93 scanf("%d%d",&x,&y); 94 edge(x,y); 95 edge(y,x); 96 } 97 pre(1,0); 98 dfs(1,0,1); 99 for(int i=1;i<=n;i++)printf("%d\n",ans[i]); 100 return 0; 101 }
P.S. 调试的时候改小了数组,提交的时候忘改回去了......改回去之后直接A掉了......有点桑心哈哈哈