关于树论动态点分治
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于树论动态点分治相关的知识,希望对你有一定的参考价值。
搬运:题意传送门:http://caioj.cn/problem.php?id=1433
前几天跟波* * * *老师一起搞这题,结果最后莫名其妙的被波老师D飞。。。
我用到的是动态点分治。
动态点分治就是基于树的重心(一棵树中,以重心为根的最大子树的节点最小)上的解法,该解法将一棵树分成若干棵子树,对每一棵子树进行重新分配,一直往下分直到每一颗树的节点数为1,上一层的重心需要连接下一层的重心,对这棵新树来进行分治递归求解。
这道题的做法:
对于新树每个节点我们用两个堆来维护,每个节点的第一个堆维护以自己为根的子树内所有节点到自己父亲节点的距离。 第二个堆维护每个子节点第一个堆的堆顶(就是每一个孩子节点的子树内里自己最远的距离) 那么相对于每一个节点,第二个堆的最大值加次大值就是子树内经过自己的最长链。
对于全局,开一个堆来维护每一个节点第二个堆的最大值加次大值,那么全局的堆顶就是答案。
至于求树上两点距离,应用了dfs序+st表的做法。dep[i]表示的是1到i节点的距离。x到y之间的距离=dep[x]+dep[y]-2*dep[最近公共祖先],那么如何来求解dep[最近公共祖先]呢 ,我用的是dfs序上ST表优化区间最小值,当然用倍增LCA也是可以的。
显然x和y的最近公共祖先是x到y路径上深度最小的点,如果我们用什么方法把路径上的点表示为连续的编号,那么用ST表求区间最小值不就解决了吗。
补充说明:
每个堆分成两个堆。但改变颜色的操作,某些状态就不合法了。那么就需要删除,但是堆每次只可以删除堆顶,所以我们把每个堆分成两个堆,一个堆记录全部状态,一个堆记录没用的状态,这样我们每次询问堆顶的时候,如果堆顶是没用的状态,那么我们就要删除。
对于不会st表的同学,请看这里。
st表可以解决rmq(区间最值)问题,LCA的树上倍增算法也是类似于该思想,算法预处理时间复杂度O(nlogn),查询时间O(1),但不支持在线修改。
利用DP的思想,得到一个数组mn[i][j],该数组表示从j到j+2^i-1的最小值,所以x到y的最小值表示为min(mn[t][x], mn[t][y-2^t+1])。
关于继承的问题,mn[0][k]自然等于k自身的值,而mn[1][k]的值为min(mn [0][k], mn[0][k+1]),不难发现,mn[i][j]=min(mn[i-1][j], mn[i-1][j+2^(i-1)]。
代码实现:
//Bin[i]表示2的i次方,Log[i]表示log(i) Bin[0]=1;for(int i=1;i<20;i++)Bin[i]=Bin[i-1]*2; Log[0]=-1;for(int i=1;i<=200000;i++)Log[i]=Log[i/2]+1; for(int i=1;i<=n;i++)mn[0][i]=a[i];、 for(int i=1;i<=Log[n];i++) for(int j=1;j<=n;j++) if(j+Bin[i]-1<=n)mn[i][j]=min(mn[i-1][j],mn[i-1][j+Bin[i-1]]);
感谢hanks_o(在度娘搜就行,ljhaoziyu刚Qtree7题的小伙伴)的倾情帮助和分析。
#include<queue> #include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> using namespace std; int Bin[20],Log[210000];//Bin[i]表示2的i次方,Log[i]就是log(i) struct node { int x,y,d,next; }a[210000];int len,last[110000]; void ins(int x,int y,int d) { len++; a[len].x=x;a[len].y=y;a[len].d=d; a[len].next=last[x];last[x]=len; } struct heap { priority_queue<int> A,B;//A堆记录存在的状态,但因为堆不支持修改,另开一个B堆记录淘汰的状态 void push (int x){A.push(x);} void erase(int x){B.push(x);} void pop() { while(B.size()&&A.top()==B.top()){A.pop();B.pop();} A.pop(); } int size(){return A.size()-B.size();} int top() { while(B.size()&&A.top()==B.top()){A.pop();B.pop();} if(A.size()==0)return 0; return A.top(); } int stop() { if(size()<2)return 0; int x=top();pop(); int y=top();push(x); return y; } }A,B[110000],C[110000]; /* 每个节点的C堆维护所有子树内的节点到自己父亲节点的距离 B堆维护所有子节点第一个堆的堆顶(最大值) 那么相对于每一个节点来说,第二个堆的最大值加次大值就是子树内经过这个节点的最长链 全局维护A堆,记录所有节点第二个堆的最大值和次大值的和。堆顶(最大值)就是答案 */ //mn[i][j]表示j到j+2^i-1点的最小深度,ys是新编号,dep[i]第i号节点表示到1号节点的距离 int z,mn[20][210000],ys[110000],dep[110000]; void dfs(int x,int f)//得dfs序,为st表准备 { mn[0][++z]=dep[x];ys[x]=z; for(int k=last[x];k;k=a[k].next) { int y=a[k].y; if(y!=f) { dep[y]=dep[x]+a[k].d; dfs(y,x); mn[0][++z]=dep[x]; } } } /* getrt 找到树的重心并存在G里。树的重心是一个点。 相比于其他点,以他为根的最大子树最小 */ bool v[110000];//v表示这个点是否被访问过 //G是divi的那棵树的中心,sum是那棵树的总节点数 //tot[i]是以i为根的树节点数,g[i]是i为根的最大子树的节点数 int G,sum,tot[110000],g[110000]; void getrt(int x,int f) { tot[x]=1;g[x]=0; for(int k=last[x];k;k=a[k].next) { int y=a[k].y; if(y!=f&&v[y]==false) { getrt(y,x); tot[x]+=tot[y]; g[x]=max(g[x],tot[y]); } } g[x]=max(g[x],sum-tot[x]);//还有到父亲这一条边的子树 if(g[x]<g[G])G=x; } //按照新的树去建父子关系,当前这一层的中心去连接下一层的重心 int fa[110000]; void divi(int x) { v[x]=true; for(int k=last[x];k;k=a[k].next) { int y=a[k].y; if(v[y]==false) { sum=tot[y];G=0; getrt(y,x); fa[G]=x;divi(G); } } } //求x到y路径上深度最小的点的深度(最近公共祖先的深度 ) int rmq(int x,int y) { x=ys[x];y=ys[y]; if(x>y)swap(x,y); int t=Log[y-x+1]; return min(mn[t][x],mn[t][y-Bin[t]+1]); } //求x到y之间的距离 int dis(int x,int y) { return dep[x]+dep[y]-2*rmq(x,y); } void turn_black(int f,int x) { if(f==x) { if(B[f].size()==1) A.push(B[f].top()); } if(fa[f]==0)return ; int ff=fa[f],D=dis(ff,x),tmp=C[f].top(); C[f].push(D); if(D>tmp) { int k1=B[ff].top()+B[ff].stop(),s1=B[ff].size(); if(tmp!=0)B[ff].erase(tmp); B[ff].push(D); int k2=B[ff].top()+B[ff].stop(),s2=B[ff].size(); if(k1<k2) { if(s1>=2)A.erase(k1); if(s2>=2)A.push(k2); } } turn_black(ff,x); } void turn_white(int f,int x) { if(f==x) { if(B[f].size()==1) A.erase(B[f].top()); } if(fa[f]==0)return ; int ff=fa[f],D=dis(ff,x),tmp=C[f].top(); C[f].erase(D); if(D==tmp) { int k1=B[ff].top()+B[ff].stop(),s1=B[ff].size(); B[ff].erase(D); if(C[f].top()!=0)B[ff].push(C[f].top()); int k2=B[ff].top()+B[ff].stop(),s2=B[ff].size(); if(k1>k2) { if(s1>=2)A.erase(k1); if(s2>=2)A.push(k2); } } turn_white(ff,x); } int cc;bool col[110000]; char ss[5]; int main() { Bin[0]=1;for(int i=1;i<20;i++)Bin[i]=Bin[i-1]*2; Log[0]=-1;for(int i=1;i<=200000;i++)Log[i]=Log[i/2]+1; int n,m,x,y,c; scanf("%d",&n); len=0;memset(last,0,sizeof(last)); for(int i=1;i<n;i++) { scanf("%d%d%d",&x,&y,&c); ins(x,y,c);ins(y,x,c); } dfs(1,0); //ST表 for(int i=1;i<=Log[z];i++) for(int j=1;j<=z;j++) if(j+Bin[i]-1<=z) mn[i][j]=min(mn[i-1][j],mn[i-1][j+Bin[i-1]]); //j到j+2^i-1的最小值等于min(前半部分最小值,后半部分最小值) G=0;g[0]=2147483647; sum=n;getrt(1,0); fa[G]=0;divi(G); for(int i=1;i<=n;i++) { turn_black(i,i); col[i]=true;cc++; } scanf("%d",&m); while(m--) { scanf("%s",ss+1); if(ss[1]==‘A‘) { if(cc<=0)printf("They have disappeared.\n"); else printf("%d\n",A.top()); } else { scanf("%d",&x); if(col[x]==true){turn_white(x,x);cc--;} else {turn_black(x,x);cc++;} col[x]=1-col[x]; } } return 0; }
以上是关于关于树论动态点分治的主要内容,如果未能解决你的问题,请参考以下文章