点分治
Posted genius777
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了点分治相关的知识,希望对你有一定的参考价值。
SCOI2018 一场毒瘤赛,DAY1T1就是一道点分的题,结果菜鸡的我根本就不知道什么是点分,然后就一脸天真地去写树剖了,调完发现自己过了大样例!!!然后就很高兴地认为自己可以骗贼多贼多的分,然后结果却是10分GG(垃圾出题人) ,啊朋友再见!!!
然后就决心要好好学习点分治!!!
【点分治???】
什么是点分治,就是把一棵子树给点分了再求链上权值(好像是一句废话)
如果一棵树我们对其直接按dfs序求链上的值,就会发现有的子树深度很吓人,然后就T得飞起来。
所以我们每次遇到了一个节点我们就对其进行点分治,具体实现是这样的:
因为我们要搜索的层数尽量少,我们最好以一颗树的重心为根开始搜(这大概就是点分的思想)
什么是树的重心,顾名思义就是树上趋*重力*衡点的节点(怎么变成物理了),别急我们换到OI的语言,就是最大子树最小的节点
比如当我们处理到了2号节点,我们不考虑其他节点,就把2的子树当成一棵要处理的树
发现3号节点的最大子树大小为2,然后是当前子树里最优秀的,所以它就是这棵树的重心了!!!
然后我们就对其处理当前子树其他节点到这个节点的距离了,然后就好了,这样我们通过这个节点作为转折点就可以处理当前子树任意两个点的路径了
1 void get_root(int v,int fa) 2 { 3 sum[v]=1;mxson[v]=0; 4 for(int i=head[v];i!=0;i=edge[i].next) 5 { 6 int to=edge[i].to; 7 if(!vis[to]&&to!=fa) 8 { 9 get_root(to,v); 10 sum[v]+=sum[to]; 11 mxson[v]=max(mxson[v],sum[to]); 12 } 13 } 14 mxson[v]=max(mxson[v],S-mxson[v]); 15 if(MX>mxson[v]) {root=v,MX=mxson[v];} 16 }
但是我们发现,这样操作其实是有BUG的,其中我们算了很多不合法的链,怎么回事???还是上面的图:
如上图我们,我们第一次点分整棵树的时候会发现2号点是重心我们就会处理4号到2号的距离,以及6号点到2号点的距离,然后我们发现4号点到2号点的距离加上并不等于两个点之间的距离,显而易见当我们处理到了一个节点的子树时,子树内部的点之间的路径是不合法的,所以我们就要处理掉这些不合法的路径我们通过代码来看看怎么实现干掉这些不合法的边:
1 void getdis(int v,int fa,int len)//dfs求距离 2 { 3 dis[++ss]=len; 4 for(int i=head[v];i!=0;i=edge[i].next) 5 { 6 int to=edge[i].to; 7 if(vis[to]||to==fa) continue; 8 getdis(to,v,len+edge[i].ww); 9 } 10 } 11 void solve(int v,int len,int add) 12 { 13 ss=0; 14 getdis(v,0,len);//处理当前子树到根节点的距离,初始值为len 15 for(int i=1;i<ss;i++) 16 for(int j=i+1;j<=ss;j++) 17 num[dis[i]+dis[j]]+=add; 18 } 19 void divide(int v) 20 { 21 solve(v,0,1);//正常操作初始距离为0,方案数增加 22 vis[v]=true; 23 for(int i=head[v];i!=0;i=edge[i].next) 24 { 25 int to=edge[i].to; 26 if(vis[to]) continue; 27 solve(to,edge[i].ww,-1);//因为这棵子树上的点的距离要加上这个子树的根节点到上一次处理的重心的距离,然后方案数要减去 28 MX=INF,S=sum[to]; 29 get_root(to,0); 30 divide(root); 31 } 32 }
这样我们就实现了点分治的处理了,可能会有人问,我们每次都要找一次重心,岂不是很不优越,但是想想会发现只要是我们找过的点我们就不会再处理了,这就是O(n)的了,而且我们还优化了dfs的层数,让其不会爆炸,这样实际跑起来实际上是优越得飞起来,不然也过不了SCOI这样的的毒瘤赛。。。
我们接下来看一道板子就好
https://www.luogu.org/problemnew/show/P3806
【代码实现】
1 #include<cstdio> 2 #include<algorithm> 3 #include<cmath> 4 #include<cstring> 5 using namespace std; 6 const int maxn=10000; 7 const int INF=1e9+7; 8 struct sd{ 9 int next,to,ww; 10 }edge[maxn*2+5]; 11 int cnt,root,num[10000005],sum[maxn+5],dis[maxn+5],mxson[maxn+5],head[maxn+5],n,m,MX,S,k,ss,ans; 12 bool vis[maxn]; 13 void add_edge(int a,int b,int w) 14 { 15 edge[++cnt].to=b; 16 edge[cnt].next=head[a]; 17 edge[cnt].ww=w; 18 head[a]=cnt; 19 } 20 void get_root(int v,int fa) 21 { 22 sum[v]=1;mxson[v]=0; 23 for(int i=head[v];i!=0;i=edge[i].next) 24 { 25 int to=edge[i].to; 26 if(!vis[to]&&to!=fa) 27 { 28 get_root(to,v); 29 sum[v]+=sum[to]; 30 mxson[v]=max(mxson[v],sum[to]); 31 } 32 } 33 mxson[v]=max(mxson[v],S-mxson[v]); 34 if(MX>mxson[v]) {root=v,MX=mxson[v];} 35 } 36 void getdis(int v,int fa,int len) 37 { 38 dis[++ss]=len; 39 for(int i=head[v];i!=0;i=edge[i].next) 40 { 41 int to=edge[i].to; 42 if(vis[to]||to==fa) continue; 43 getdis(to,v,len+edge[i].ww); 44 } 45 } 46 void solve(int v,int len,int add) 47 { 48 ss=0; 49 getdis(v,0,len); 50 for(int i=1;i<ss;i++) 51 for(int j=i+1;j<=ss;j++) 52 num[dis[i]+dis[j]]+=add; 53 } 54 void divide(int v) 55 { 56 solve(v,0,1); 57 vis[v]=true; 58 for(int i=head[v];i!=0;i=edge[i].next) 59 { 60 int to=edge[i].to; 61 if(vis[to]) continue; 62 solve(to,edge[i].ww,-1); 63 MX=INF,S=sum[to]; 64 get_root(to,0); 65 divide(root); 66 } 67 } 68 int main() 69 { 70 memset(vis,false,sizeof(vis)); 71 scanf("%d%d",&n,&m); 72 for(int i=1;i<n;i++) 73 { 74 int a,b,w; 75 scanf("%d%d%d",&a,&b,&w); 76 add_edge(a,b,w); 77 add_edge(b,a,w); 78 } 79 MX=INF,S=n; 80 get_root(1,0); 81 divide(root); 82 for(int i=1;i<=m;i++) 83 { 84 int k; 85 scanf("%d",&k); 86 if(num[k]) printf("AYE\\n"); 87 else printf("NAY\\n"); 88 } 89 return 0; 90 }
以上是关于点分治的主要内容,如果未能解决你的问题,请参考以下文章