题解 P5180 【【模板】支配树】

· · 题解

说实话题解全在注释里了

事实上我是三篇博客一起看才理解的

这里推荐两篇不错的博客

证明非常严谨,也很好懂

神仙“性感”理解法

#include<iostream>
#include<cstdio>
#include<cstring>
#define For(i,a,b) for(int i=a;i<=b;i++)
#define Ford(i,a,b) for(int i=a;i>=b;i--)
const int N=2e5+100,M=3e5+1000;
void read(int &x)
{
    int t=0,opt=1;
    char c=getchar();
    while (c<'0'||c>'9') (c=='-'?opt=-1:0),c=getchar();
    while ('0'<=c&&c<='9')
    {
        t=t*10+c-'0';
        c=getchar();
    }
    x=t*opt;return ;
}
int n,m,x,y,idom[N],sdom[N];
int nex[N],pre[N],sdom_nex[N],idom_nex[N];
int Next[2*M+N+N],d[2*M+N+N],tol=0;
void Link(int *nex,int x,int y)
{
    Next[tol]=nex[x];
    nex[x]=tol;
    d[tol]=y;
    tol++;
}
int dfn[N],cnt,pos[N],pa[N];
void dfs(int now)
{
    dfn[now]=++cnt;pos[cnt]=now;
    for (int p=nex[now];p!=-1;p=Next[p]) if (!dfn[d[p]]) 
        pa[d[p]]=now,dfs(d[p]);
}
namespace Set
{
    int pa[N],min[N];
    int Find(int x)
    {
        if (x==pa[x]) return x;
        int ans=Find(pa[x]);
        if (dfn[sdom[min[pa[x]]]]<dfn[sdom[min[x]]]) min[x]=min[pa[x]];
        return pa[x]=ans;
    }
} ;
void Tarjan()
{
    Ford(i,n,2) //将搜索树上s到他的路径理解为s到他的第一条路径
    {
        int now=pos[i];
        //把sdom视为树外边(因为树上路径一定是第一条路径,sdom不可能从小于当前根的子树过来)
        //枚举 d[p]->now  ,从他们到 他们和now的LCA 的路上取最小的sdom[]
        //因为经过d[p]到now的合法且可能更新sdom[now]的路径 
        //1)起点一定是now的祖先 (sdom[now]<=fa[now],且引理:dfn小于now的点到now一定经过其和now的祖先)
        //  证引理:想象一下有一棵建了一半的搜索树,一个点x,他要到一个还没有遍历的点y(dfn>x),那么一定会从他或者他祖先上的一个点走过去
        //2)一定是先经过一段非树边(或者是LCA到LCA孩子的一小段)(即sdom),然后再随便走(树边或非树边)到d[p]
        for(int p=pre[now];p!=-1;p=Next[p])     
        {   
            Set::Find(d[p]);//d[p]的祖宗最多有一个点dfn小于now,且min等于自己,所以是对的
            //并查集min存的是他到祖宗路上sdom最小的点,是为了更新idom
            if (dfn[sdom[Set::min[d[p]]]]<dfn[sdom[now]]) sdom[now]=sdom[Set::min[d[p]]];
        }
        Link(sdom_nex,sdom[now],now);//为了算支配点
        Set::pa[now]=pa[now];//他的半支配点解决了,并到并查集里
        now=pos[i-1];//此时sdom可能为now的点都解决了
        for(int p=sdom_nex[now];p!=-1;p=Next[p])
        {
            Set::Find(d[p]);//此时d[p]的祖宗一定是now,min只会取到路径上点(也有可能取到now,不过此时sdom[now]=now),所以是对的
            //https://blog.csdn.net/litble/article/details/83019578
            if (sdom[Set::min[d[p]]]==now) idom[d[p]]=now;
                //对于所有支配点,搜索树最深的支配点才是idom
                //先考虑now是不是d[p]的支配点,如果now上面没有人可以不经过now到   now和d[p] 路径中的点(非now),那么now支配d[p]
            else idom[d[p]]=Set::min[d[p]];
                //如果now不是d[p]支配点,那么的下一个d[p]的可能支配点一定是now+->u ·->d[p]的sdom最小的那个u,然后在判断sdom[u]是不是u支配点
                //这一过程可以理解为递归,然后相当与用idom记忆化 即idom[d[p]]=idom[min[d[p]]]
                //又因为min[d[p]]小于now,所以先记下来然后填坑
        }
    }
    For(i,2,n) if (idom[pos[i]]!=sdom[pos[i]]) idom[pos[i]]=idom[idom[pos[i]]];
    For(i,2,n) Link(idom_nex,idom[i],i);
}
int ans[N];
void dfs_ans(int now)
{
    ans[now]=1;
    for(int p=idom_nex[now];p!=-1;p=Next[p]) dfs_ans(d[p]),ans[now]+=ans[d[p]];
}
int main()
{
//  freopen("xx.in","r",stdin);
    memset(nex,-1,sizeof(nex));memset(pre,-1,sizeof(pre));
    memset(idom_nex,-1,sizeof(idom_nex));
    memset(sdom_nex,-1,sizeof(sdom_nex));
    read(n);read(m);
    For(i,1,n) Set::min[i]=i,Set::pa[i]=i,sdom[i]=i;//dominate 主宰  这样初始化,保证答案正确性
    For(i,1,m) read(x),read(y),Link(nex,x,y),Link(pre,y,x);
    dfs(1);Tarjan();
    dfs_ans(1);For(i,1,n) printf("%d ",ans[i]);
    return 0;
}