CF1009F Dominant Indices(树上DSU/长链剖分)
题目大意:
就是给你一棵以1为根的树,询问每一个节点的子树内节点数最多的深度(相对于这个子树根而言)若有多解,输出最小的。
解题思路:
这道题用树链剖分,两种思路:
1.树上DSU
首先想一下最暴力的算法:统计子树每个深度节点的个数(桶)相当于以每个节点为根遍历子树搜索一遍答案,这样做时间复杂度是O(n2),显然过不去。
考虑一下优化。假如说我们模拟一下搜索答案的过程,我们发现在每一次暴搜时都会在桶中添加一些答案。而这些答案的整体只会对该节点及其祖先产生贡献,也就是说,只有该节点以及其祖先的桶中才一定会有这个答案的整体,也只会对该节点及以下非同祖子树答案产生干扰。也就是说,搜索一棵树时,其答案可以直接加到其祖先上,所以对于最后一颗子树,因为之后不会再干扰其他子树所以其答案可以直接上传至父节点。那么这个最后搜索的节点的子树应该越大越好,那么就可以使用树链剖分解决了。答案更新时,最后搜最大的即可。
对于时间复杂度,每次搜索最坏为O(n),最大儿子上传是O(1),小儿子上传为O(n),轻儿子log2n个所以时间复杂度为O(logn)
上代码:
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 struct pnt{ 5 int hd; 6 int dp; 7 int wgt; 8 int mxs; 9 int ans; 10 }p[3000000]; 11 struct ent{ 12 int twd; 13 int lst; 14 }e[3000000]; 15 struct pr{ 16 int x,y; 17 bool friend operator == (pr a,pr b){return (a.x==b.x)&&(a.y==b.y);} 18 bool friend operator > (pr a,pr b){if(a.y==b.y)return a.x<b.x;return a.y>b.y;} 19 bool friend operator < (pr a,pr b){if(a.y==b.y)return a.x>b.x;return a.y<b.y;} 20 bool friend operator <= (pr a,pr b){return ((a<b)||(a==b));} 21 bool friend operator >= (pr a,pr b){return ((a>b)||(a==b));} 22 }; 23 class Prq{ 24 public: 25 bool bol(void) 26 { 27 return (bool)(siz==0); 28 } 29 void maxs(int &b) 30 { 31 b=line[1].x; 32 } 33 void del(void) 34 { 35 line[1]=line[siz--]; 36 int nw,nx; 37 nw=1; 38 while((nw<<1)<=siz) 39 { 40 nx=nw<<1; 41 if(nx<siz&&line[nx]<line[nx+1]) 42 nx++; 43 if(line[nx]<=line[nw]) 44 break; 45 std::swap(line[nw],line[nx]); 46 nw=nx; 47 } 48 return ; 49 } 50 void ins(int a,int b) 51 { 52 pr tmp=(pr){a,b}; 53 line[++siz]=tmp; 54 int nx,nw; 55 nw=siz; 56 while(nw>1) 57 { 58 nx=nw>>1; 59 if(line[nx]>=line[nw]) 60 break; 61 std::swap(line[nx],line[nw]); 62 nw=nx; 63 } 64 return ; 65 } 66 void dst(void) 67 { 68 siz=0; 69 return ; 70 } 71 private: 72 pr line[10000000]; 73 int siz; 74 }Q; 75 int n; 76 int cnt; 77 int num[2000001]; 78 void ade(int f,int t) 79 { 80 cnt++; 81 e[cnt].twd=t; 82 e[cnt].lst=p[f].hd; 83 p[f].hd=cnt; 84 } 85 void Basic_dfs(int x,int f) 86 { 87 p[x].dp=p[f].dp+1; 88 p[x].wgt=1; 89 int maxs=-1; 90 for(int i=p[x].hd;i;i=e[i].lst) 91 { 92 int to=e[i].twd; 93 if(to==f) 94 continue; 95 Basic_dfs(to,x); 96 p[x].wgt+=p[to].wgt; 97 if(maxs<p[to].wgt) 98 { 99 maxs=p[to].wgt; 100 p[x].mxs=to; 101 } 102 } 103 } 104 void Build_dfs(int x,int f) 105 { 106 num[p[x].dp]++; 107 Q.ins(p[x].dp,num[p[x].dp]); 108 for(int i=p[x].hd;i;i=e[i].lst) 109 { 110 int to=e[i].twd; 111 if(to==f) 112 continue; 113 Build_dfs(to,x); 114 } 115 } 116 void Destory_dfs(int x,int f) 117 { 118 num[p[x].dp]--; 119 for(int i=p[x].hd;i;i=e[i].lst) 120 { 121 int to=e[i].twd; 122 if(to==f) 123 continue; 124 Destory_dfs(to,x); 125 } 126 } 127 void DSU_dfs(int x,int f,bool hvs) 128 { 129 if(!x) 130 return ; 131 for(int i=p[x].hd;i;i=e[i].lst) 132 { 133 int to=e[i].twd; 134 if(to==f||to==p[x].mxs) 135 continue; 136 DSU_dfs(to,x,false); 137 } 138 DSU_dfs(p[x].mxs,x,true); 139 for(int i=p[x].hd;i;i=e[i].lst) 140 { 141 int to=e[i].twd; 142 if(to==f||to==p[x].mxs) 143 continue; 144 Build_dfs(to,x); 145 } 146 num[p[x].dp]++; 147 Q.ins(p[x].dp,num[p[x].dp]); 148 Q.maxs(p[x].ans); 149 p[x].ans-=p[x].dp; 150 if(hvs) 151 return ; 152 Destory_dfs(x,f); 153 Q.dst(); 154 return ; 155 } 156 int main() 157 { 158 scanf("%d",&n); 159 for(int i=1;i<n;i++) 160 { 161 int x,y; 162 scanf("%d%d",&x,&y); 163 ade(x,y); 164 ade(y,x); 165 } 166 Basic_dfs(1,1); 167 DSU_dfs(1,1,true); 168 for(int i=1;i<=n;i++) 169 printf("%d\n",p[i].ans); 170 return 0; 171 }
2.树的长链剖分:
这个方法比上面的方法跑得快。
首先,观察那种最朴素的全搜一遍的O(n2)算法,它的瓶颈在于,统计答案时同一深度非同父的节点,其答案可能互相干扰,那么我们为何不合理安排内存使其答案不会互相被访问到而会同时被祖先访问到。类似于一个树链剖分序。类似长链先搜,短链后搜的剖分序。使用不同的下标索引使桶中的变量不会在深度环境下发生冲突,再logn统计答案就可以了,其实是O(nlogn)但重建部分比较简单常数较小且其最坏复杂度很难达到所以速度相当惊人。
上代码:
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 struct pnt{ 5 int hd; 6 int dp; 7 int mxs; 8 bool vis; 9 int ind; 10 int ans; 11 int wsa; 12 }p[1000001]; 13 struct ent{ 14 int twd; 15 int lst; 16 }e[10000001]; 17 int n,m; 18 int cnt; 19 int wh; 20 int tmp[1000001]; 21 void ade(int f,int t) 22 { 23 cnt++; 24 e[cnt].twd=t; 25 e[cnt].lst=p[f].hd; 26 p[f].hd=cnt; 27 } 28 void Basic_dfs(int x,int f) 29 { 30 p[x].dp=p[f].dp+1; 31 p[x].wsa=p[x].dp; 32 for(int i=p[x].hd;i;i=e[i].lst) 33 { 34 int to=e[i].twd; 35 if(to==f) 36 continue; 37 Basic_dfs(to,x); 38 p[x].wsa=std::max(p[x].wsa,p[to].wsa); 39 if(p[to].wsa>p[p[x].mxs].wsa) 40 { 41 p[x].mxs=to; 42 } 43 } 44 } 45 void Gund_dfs(int x,int f) 46 { 47 tmp[p[x].ind]=1; 48 if(p[x].mxs) 49 { 50 p[p[x].mxs].ind=p[x].ind+1; 51 Gund_dfs(p[x].mxs,x); 52 p[x].ans=p[p[x].mxs].ans+1; 53 } 54 for(int i=p[x].hd;i;i=e[i].lst) 55 { 56 int to=e[i].twd; 57 if(to==f||to==p[x].mxs) 58 continue; 59 p[to].ind=wh; 60 wh+=p[to].wsa-p[to].dp+1; 61 Gund_dfs(to,x); 62 for(int j=0;j<=p[to].wsa-p[to].dp;j++) 63 { 64 tmp[p[x].ind+j+1]+=tmp[p[to].ind+j]; 65 if(tmp[p[x].ind+j+1]>tmp[p[x].ind+p[x].ans]||(tmp[p[x].ind+j+1]==tmp[p[x].ind+p[x].ans]&&p[x].ans>j+1)) 66 p[x].ans=j+1; 67 } 68 } 69 if(tmp[p[x].ind+p[x].ans]==1) 70 p[x].ans=0; 71 } 72 int main() 73 { 74 scanf("%d",&n); 75 for(int i=1;i<n;i++) 76 { 77 int x,y; 78 scanf("%d%d",&x,&y); 79 ade(x,y); 80 ade(y,x); 81 } 82 Basic_dfs(1,1); 83 wh=p[1].wsa; 84 Gund_dfs(1,1); 85 for(int i=1;i<=n;i++) 86 printf("%d\n",p[i].ans); 87 return 0; 88 }