支配树详解

什么是支配树?
对于一张有向图,确定一个根,如果根到 x 的每条路径都经过 y,那么称 y 是 x 支配点。求出原图的一个 dfs 树,那么 x 的支配点一定在 x 到根的链上。如果每个点向自己深度最深的支配点连边,就构成了支配树。

明确一些有向图 dfs 树的性质:
1.树边总是由 dfn 小的点指向 dfn 大的点,非树边(两端点无祖先关系)总是由 dfn 大的点指向 dfn 小的点。
2.若 v,w 是图中节点且 dfn[v]<=dfn[w],则任意从 v 到 w 的路径必然包含它们在 dfs 树中的一个公共祖先。

如果存在 y 到 x 的一条路径,使得路径上的点(除 x,y)dfn 都大于 x,那么称 y 是 x 的一个半支配点。可以证明 x 的半支配点一定是 x 的祖先,我们记 sdom[x] 为 x 半支配点中深度最小的点。

求 sdom:考虑枚举路径上最后一个点 z, s d o m [ x ] = min ⁡ { s d o m [ z ] ∣ ( z , x ) ∈ E , d f n [ z ] > d f n [ x ] } sdom[x]=\min\{sdom[z]|(z,x)\in E,dfn[z]>dfn[x]\} sdom[x]=min{sdom[z](z,x)E,dfn[z]>dfn[x]}

怎么求支配点?对于一个点 x,sdom[x] 到 x 之间的点一定不会是支配点。对于 x 到根的每个点 y,把不合法的点删掉后剩下的深度最深的点就是支配点。

实现:
1.求 sdom:相当于求 x 到根的路径上 dfn 大于 dfn[x] 的点的最小值。按 dfn 从大到小处理,带权并查集维护子树。

2.求 idom:相当于求 x 到根的路径上深度大于 sdom[x] 的点的最小值。同样按 sdom[x] 从大到小维护带权并查集。注意桶排。

(可能)比较好写的做法:求完 sdom 以后 sdom[x] 向 x 连边。把图变为 DAG.

洛谷模板:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=200010,M=300010;
struct edge{
    int to,next;
}ed[M];
int sz,head[N],dfn[N],deep[N],fa[N],size[N],a[N],c[N],tim,sdom[N],idom[N],wb[N],lk[N],tr[M];
vector <int> b[N],d[N];
void add_edge(int from,int to)
{
    ed[++sz].to=to;
    ed[sz].next=head[from];
    head[from]=sz;
}
int read()
{
    int x=0;char c=getchar(),flag='+';
    while(!isdigit(c)) flag=c,c=getchar();
    while(isdigit(c)) x=x*10+c-'0',c=getchar();
    return flag=='-'?-x:x;
}
inline int chk(int x,int y)
{
    if(!x||!y) return x|y;
    if(dfn[x]<dfn[y]) return x;
    return y;
}
inline int chk2(int x,int y)
{
    if(dfn[sdom[x]]<dfn[sdom[y]]) return x;
    return y;
}
void dfs(int u)
{
    dfn[u]=++tim;
    a[tim]=u;
    d[deep[u]].push_back(u);
    for(int i=head[u];i;i=ed[i].next)
    {
        int v=ed[i].to;
        if(dfn[v]) continue;
        tr[i]=1;
        deep[v]=deep[u]+1;
        dfs(v);
    }
}
int find(int x)
{
    if(x==fa[x]) return x;
    int y=fa[x];
    fa[x]=find(y);
    if(y^fa[x]) c[x]=chk2(c[x],c[y]);	//带权并查集 c[x] 维护 x 到 fa 的最小值(不包括 fa)
    return fa[x];
}
int main()
{
    int n=read(),m=read();
    for(int i=1;i<=m;i++)
    {
        int u=read(),v=read();
        add_edge(u,v);
        b[v].push_back(u);
    }
    deep[1]=1;
    dfs(1);
    
    //get sdom
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=n;i>=1;i--)
    {
        int x=a[i];
        for(int j=0;j<b[x].size();j++)
        {
            int y=b[x][j];
            sdom[x]=chk(sdom[x],y);
            find(y);
            sdom[x]=chk(sdom[x],chk(sdom[c[y]],sdom[c[fa[y]]]));
        }
        c[x]=x;
        for(int j=head[x];j;j=ed[j].next)
        {
            int y=ed[j].to;
            if(tr[j]) fa[y]=x;	//特判树边
        }
    }
    
    //get idom
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=1;i<=n;i++) wb[deep[sdom[i]]]++;
    for(int i=n;i>=1;i--) wb[i]+=wb[i+1];
    for(int i=1;i<=n;i++) a[wb[deep[sdom[i]]]--]=i;
    int p=n+1;
    for(int i=1;i<=n;i++)
    {
        int x=a[i];
        while(p>deep[sdom[x]]) 
        {
            p--;
            for(int j=0;j<d[p].size();j++) 
            {
                int y=d[p][j];
                c[y]=y;
                for(int k=head[y];k;k=ed[k].next)
                {
                    int z=ed[k].to;
                    if(tr[k]) fa[z]=y;	//特判树边
                }
            }
        }
        int y=find(x);
        if(sdom[c[x]]==sdom[x]) idom[x]=sdom[x];
        else lk[x]=c[x];
    }
    for(p=1;p<=n;p++)
    {
        for(int i=0;i<d[p].size();i++)
        {
            int u=d[p][i];
            if(!idom[u]) idom[u]=idom[lk[u]];
        }
    }
    idom[1]=0;
    for(int i=1;i<=n;i++) size[i]=1;
    for(p=n;p>=1;p--)
    {
        for(int i=0;i<d[p].size();i++)
        {
            int u=d[p][i];
            size[idom[u]]+=size[u];
        }
    }
    for(int i=1;i<=n;i++) cout<<size[i]<<' ';
    return 0;
}
/*by DT_Kang*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值