圖論雜項細節梳理&模板(虛樹,圓方樹,仙人掌,還有。。。)
阿新 • • 發佈:2019-02-02
準備 while class www 哪裏 容易 return mes 模板
虛樹
%自為風月馬前卒巨佬%
用於優化一類樹形DP問題。
當狀態轉移只和樹中的某些關鍵點有關的時候,我們把這些點和它們兩兩之間的LCA弄出來,以點的祖孫關系連成一棵新的樹,這就是虛樹。
容易證明,如果關鍵點數量為\(m\),則虛樹點數不超過\(2m\)。
虛樹的構建
dfs原樹,對點進行dfn標號,並將關鍵點按dfn從小到大排序。
搞個棧,棧內的點滿足:都在從棧頂的點到原樹的根的一條鏈上。
現在我們準備加入一個點\(x\)
直接加可能破壞一條鏈的性質,於是把棧頂的元素彈掉直到可以加入為止。求個LCA討論一波,具體參考代碼。
彈棧的時候就可以連好虛樹邊了。
int p=0;//st[0]代表一個dfn為0的0號空點,方便處理 sort(a+1,a+m+1,cmp);//按dfn排序 for(int i=1;i<=m;st[++p]=a[i++]){ int y=lca(a[i],st[p]); while(p&&dfn[st[p-1]]>=dfn[y]) add(st[p-1],st[p]),--p; if(y!=st[p])add(y,st[p]),st[p]=y;//註意判斷 } while(p>1)add(st[p-1],st[p]),--p;//st[1]應為虛樹根
當然,可能有些題的虛樹在關鍵點之間也有限制?寫出來都不一樣。
比如洛谷P2495 [SDOI2011]消耗戰
有一個固定的\(1\)號點,再就是只能保留沒有祖孫關系(\(1\)號點除外)的關鍵點。寫法也有好幾處不一樣
int p=0;st[0]=1; sort(h+1,h+k+1,cmp); for(R i=1;i<=k;++i){ if(!p){st[++p]=h[i];continue;} R x=h[i],y=lca(x,st[p]); if(y==st[p])continue; while(p&&l[st[p-1]]>=l[y])add(st[p-1],st[p]),--p; if(y!=st[p])add(y,st[p]),st[p]=y; st[++p]=x; } while(p)add(st[p-1],st[p]),--p;
所以看來虛樹這個東西關鍵不在於背板子,而在於靈活運用。
題目
洛谷P3233 [HNOI2014]世界樹
每個詢問建虛樹,兩遍dfs確定每個虛樹上的點被哪裏管理(第一遍從下往上更新,第二遍從上往下)
對於兩個虛樹點中間的部分,倍增找出臨界點,兩邊的size分開貢獻。
找臨界點是個極其惡心的討論就對了。
倍增代碼短常數大,表示基本沒有看到別的小於2.5k的代碼。。。
#include<bits/stdc++.h> #define R register int #define G if(++ip==ie)if(fread(ip=buf,1,SZ,stdin)) using namespace std; const int SZ=1<<19,N=3e5+9,M=2*N; char buf[SZ],*ie=buf+SZ,*ip=ie-1; inline int in(){ G;while(*ip<'-')G; R x=*ip&15;G; while(*ip>'-'){x*=10;x+=*ip&15;G;} return x; } int p,he[N],ne[M],to[M],l[N],sr[N],d[N],o[N],fa[N][20]; void dfs(R x,R f){ l[x]=++p;sr[x]=1;d[x]=d[f]+1;fa[x][0]=f; for(R&i=o[x];(fa[x][i+1]=fa[fa[x][i]][i]);++i); for(R i=he[x];i;i=ne[i]) if(to[i]!=f)dfs(to[i],x),sr[x]+=sr[to[i]]; } int lca(R x,R y){ if(d[x]<d[y])swap(x,y); for(R i=o[x];~i;--i) if(d[fa[x][i]]>=d[y])x=fa[x][i]; if(x==y)return x; for(R i=o[x];~i;--i) if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i]; return fa[x][0]; } namespace VT{ int h[N],a[N],st[N],he[N],ne[N],tp[N],mn[N],id[N],si[N],ans[N],ok[N]; inline bool cmp(R x,R y){ return l[x]<l[y]; } inline void add(R x,R y){ ne[y]=he[x];he[x]=tp[y]=y; for(R i=0,k=d[y]-d[x]-1;k;k>>=1,++i) if(k&1)tp[y]=fa[tp[y]][i]; } inline void chkmn(R x,R y){ R t=mn[y]+abs(d[y]-d[x]); if(mn[x]>t)mn[x]=t,id[x]=id[y]; else if(mn[x]==t&&h[id[x]]>h[id[y]])id[x]=id[y]; } void calc(R x,R y){ R z=y,p=d[x]-mn[x]+d[y]+mn[y]; if(p&1)p=(p+1)>>1; else p=(p>>1)+(h[id[x]]<h[id[y]]||mn[x]+d[x]==mn[y]+d[y]); for(R i=0,k=d[y]-p;k;k>>=1,++i) if(k&1)z=fa[z][i]; ans[id[y]]+=sr[z]-si[y]; ans[id[x]]+=sr[tp[y]]-sr[z]; he[y]=si[y]=0; } void dfsup(R x){ if(!ok[x])mn[x]=M; for(R y=he[x];y;y=ne[y]) dfsup(y),chkmn(x,y),si[x]+=sr[tp[y]]; } void dfsdn(R x){ for(R y=he[x];y;y=ne[y]) chkmn(y,x),dfsdn(y),calc(x,y); } void work(){ R m=in(),p=0; for(R i=1;i<=m;++i){ R x=h[i]=a[i]=in(); mn[x]=0,id[x]=i,ok[x]=1; } sort(a+1,a+m+1,cmp); for(R i=1;i<=m;st[++p]=a[i++]){ R y=lca(a[i],st[p]); while(p&&l[st[p-1]]>=l[y])add(st[p-1],st[p]),--p; if(y!=st[p])add(y,st[p]),st[p]=y; } while(p)add(st[p-1],st[p]),--p; dfsup(0);dfsdn(0);he[0]=0; for(R i=1;i<=m;++i)printf("%d ",ans[i]),ok[h[i]]=ans[i]=0;puts(""); } } int main(){ R n=in();to[he[0]=1]=1; for(R i=1,p=1;i<n;++i){ R x=in(),y=in(); ne[++p]=he[x];to[he[x]=p]=y; ne[++p]=he[y];to[he[y]=p]=x; } dfs(0,0); for(R q=in();q;--q)VT::work(); return 0; }
仙人掌
orzyyb
圓方樹
orzyl
更新中
圖論雜項細節梳理&模板(虛樹,圓方樹,仙人掌,還有。。。)