图论杂项细节梳理&模板(虚树,圆方树,仙人掌,还有。。。)

Posted flashhu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图论杂项细节梳理&模板(虚树,圆方树,仙人掌,还有。。。)相关的知识,希望对你有一定的参考价值。

虚树

%自为风月马前卒巨佬%
用于优化一类树形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

更新中

以上是关于图论杂项细节梳理&模板(虚树,圆方树,仙人掌,还有。。。)的主要内容,如果未能解决你的问题,请参考以下文章

仙人掌&圆方树

圆方树学习

BZOJ2125: 最短路 圆方树(静态仙人掌)

Luogu4606 SDOI2018 战略游戏 圆方树虚树链并

圆方树总结

圆方树与仙人掌