1. 程式人生 > >圖論雜項細節梳理&模板(虛樹,圓方樹,仙人掌,還有。。。)

圖論雜項細節梳理&模板(虛樹,圓方樹,仙人掌,還有。。。)

準備 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

更新中

圖論雜項細節梳理&模板(虛樹,圓方樹,仙人掌,還有。。。)