[模板]点分治
Posted ressed
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[模板]点分治相关的知识,希望对你有一定的参考价值。
用途
大规模地处理树上路径
做法
先考虑对x为根的子树做dfs来处理x子树到x的路径,然后统计答案,然后再递归地做x的儿子...
然而当树退化成链时,最差复杂度是$O(n^2)$的
类比一维中二分的做法,其实是使左右区间尽量平均,那我们也让我们要处理的点的子树大小尽量平均
具体来说,我们每次想要做x这个子树的时候,会想先在这个子树中找到一个点root,使得它的每个子树大小的最大值最小,然后将root作为这棵子树的新根,再继续处理路径
我们通过dfs来统计子树的大小以选定root,需要注意的是,由于某个点的父亲将来也有可能变成它的孩子(如果它被选为根的话),那就也需要算父亲作为它的孩子时的大小,其实就是S-size[x],其中S是这棵树的总大小
这样复杂度就是$O(nlogn)$的了
然而还有一个很重要的问题:我在以x为根去dfs找路径的时候,可能会找到两个端点在同一个子树的情况,这显然是不合法的。我们可以有两种方法来处理这种情况
1.(适用于大多数情况)在处理x的子树y的时候,先钦定住要走x到y这条边,也同样计算一波路径,这样算出来的就一定是刚才的不合法情况,减掉就完事了
2.(适用于可以单独地对于某条路径O(1)地统计答案)我不做x,而是对x的每个儿子y,先用子树y统计答案,再用子树y更新答案,为以后的统计铺路,这样做,我统计到的答案一定不在同一个子树里,也就不会出现上面的问题。(例:bzoj2599 Race)
例题:
luogu4178 求距离不超过K的路径数
1 #include<bits/stdc++.h> 2 #define pa pair<int,int> 3 #define ll long long 4 using namespace std; 5 const int maxn=40040; 6 7 inline ll rd(){ 8 ll x=0;char c=getchar();int neg=1; 9 while(c<‘0‘||c>‘9‘){if(c==‘-‘) neg=-1;c=getchar();} 10 while(c>=‘0‘&&c<=‘9‘) x=x*10+c-‘0‘,c=getchar(); 11 return x*neg; 12 } 13 14 struct Edge{ 15 int a,b,l,ne; 16 }eg[maxn*2]; 17 int egh[maxn],ect; 18 int N,K; 19 int mins,smsiz,pct; 20 int siz[maxn],dis[maxn],ans,fa[maxn]; 21 bool flag[maxn]; 22 23 inline void adeg(int a,int b,int l){ 24 eg[++ect].a=a;eg[ect].b=b;eg[ect].l=l; 25 eg[ect].ne=egh[a];egh[a]=ect; 26 } 27 28 void getroot(int &rt,int x){ 29 int mm=0;siz[x]=1; 30 for(int i=egh[x];i!=-1;i=eg[i].ne){ 31 int b=eg[i].b;if(b==fa[x]||flag[b]) continue; 32 fa[b]=x;getroot(rt,b); 33 siz[x]+=siz[b];mm=max(siz[b],mm); 34 }mm=max(smsiz-siz[x],mm); 35 if(mm<mins) mins=mm,rt=x; 36 } 37 38 void getdis(int x,int f,int d){ 39 dis[++pct]=d; 40 for(int i=egh[x];i!=-1;i=eg[i].ne){ 41 int b=eg[i].b;if(b==f||flag[b]) continue; 42 getdis(b,x,eg[i].l+d); 43 } 44 } 45 46 int calc(int x,int tt){ 47 pct=0;getdis(x,0,tt);int re=0; 48 sort(dis+1,dis+pct+1); 49 int l=1,r=pct; 50 while(l<r){ 51 if(dis[l]+dis[r]<=K) re+=r-l,l++; 52 else r--; 53 }return re; 54 } 55 56 void solve(int x){ 57 flag[x]=1; 58 ans+=calc(x,0); 59 for(int i=egh[x];i!=-1&&i;i=eg[i].ne){ 60 int b=eg[i].b;if(flag[b]) continue; 61 ans-=calc(b,eg[i].l); 62 mins=0x3f3f3f3f,smsiz=siz[b]; 63 int root=0;getroot(root,b); 64 siz[fa[root]]=smsiz-siz[root]; 65 solve(root); 66 } 67 } 68 69 int main(){ 70 int i,j,k; 71 //freopen("4178.in","r",stdin); 72 N=rd();memset(egh,-1,sizeof(egh)); 73 for(i=1;i<N;i++){ 74 int a=rd(),b=rd(),c=rd(); 75 adeg(a,b,c);adeg(b,a,c); 76 }K=rd(); 77 smsiz=N;mins=0x3f3f3f3f; 78 int root=0;getroot(root,1); 79 solve(root); 80 printf("%d ",ans); 81 82 return 0; 83 }
bzoj2599/luogu4149 求距离为K的路径的最小边数
1 #include<bits/stdc++.h> 2 #define pa pair<int,int> 3 #define ll long long 4 using namespace std; 5 const int maxn=200020,maxk=1000010; 6 7 inline ll rd(){ 8 ll x=0;char c=getchar();int neg=1; 9 while(c<‘0‘||c>‘9‘){if(c==‘-‘) neg=-1;c=getchar();} 10 while(c>=‘0‘&&c<=‘9‘) x=x*10+c-‘0‘,c=getchar(); 11 return x*neg; 12 } 13 14 int N,K; 15 struct Edge{ 16 int b,ne,l; 17 }eg[maxn*2]; 18 int egh[maxn],ect; 19 int siz[maxn],pct; 20 bool flag[maxn]; 21 int root,ms,smsiz; 22 int dis[maxk],ans=0x3f3f3f3f; 23 queue<int> dq; 24 25 26 inline void adeg(int a,int b,int l){ 27 eg[++ect].b=b;eg[ect].l=l;eg[ect].ne=egh[a];egh[a]=ect; 28 } 29 30 void getroot(int x,int f){ 31 siz[x]=1;int mm=0; 32 for(int i=egh[x];i;i=eg[i].ne){ 33 int b=eg[i].b;if(flag[b]||b==f) continue; 34 getroot(b,x); 35 siz[x]+=siz[b];mm=max(siz[b],mm); 36 }mm=max(smsiz-siz[x],mm); 37 if(mm<ms) ms=mm,root=x; 38 } 39 40 void getdis(int x,int f,int d,int dep){ 41 if(d<=K){ 42 if(dis[d]>2e5) dq.push(d); 43 dis[d]=min(dis[d],dep); 44 }else return; 45 for(int i=egh[x];i;i=eg[i].ne){ 46 int b=eg[i].b;if(flag[b]||b==f) continue; 47 getdis(b,x,d+eg[i].l,dep+1); 48 } 49 } 50 void calans(int x,int f,int d,int dep){ 51 if(d<=K){ 52 if(dis[K-d]<=2e5) ans=min(ans,dep+dis[K-d]); 53 }else return; 54 for(int i=egh[x];i;i=eg[i].ne){ 55 int b=eg[i].b;if(flag[b]||b==f) continue; 56 calans(b,x,d+eg[i].l,dep+1); 57 } 58 } 59 60 inline void solve(int x){ 61 flag[x]=1; 62 for(int i=egh[x];i;i=eg[i].ne){ 63 int b=eg[i].b;if(flag[b]) continue; 64 calans(b,x,eg[i].l,1); 65 getdis(b,x,eg[i].l,1); 66 }while(!dq.empty()) dis[dq.front()]=0x3f3f3f3f,dq.pop(); 67 int ss=smsiz; 68 for(int i=egh[x];i;i=eg[i].ne){ 69 int b=eg[i].b;if(flag[b]) continue; 70 ms=0x3f3f3f3f;smsiz=siz[b]>siz[x]?ss-siz[x]:siz[b]; 71 getroot(b,x);solve(root); 72 } 73 } 74 75 int main(){ 76 int i,j,k; 77 N=rd(),K=rd(); 78 for(i=1;i<N;i++){ 79 int a=rd()+1,b=rd()+1,c=rd(); 80 adeg(a,b,c);adeg(b,a,c); 81 }memset(dis,127,sizeof(dis));dis[0]=0; 82 smsiz=N;ms=0x3f3f3f3f;getroot(1,0); 83 solve(root); 84 if(ans<=2e5) printf("%d ",ans); 85 else printf("-1 "); 86 return 0; 87 }
以上是关于[模板]点分治的主要内容,如果未能解决你的问题,请参考以下文章