虛樹
%自爲風月馬前卒巨佬%
用於優化一類樹形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;
}