支配树(以HDU4694 Important Sisters为例)

71 篇文章 0 订阅
54 篇文章 0 订阅

定义

在一个流程图(单源有向图)中,如果从源点到某个点p必须要经过某个点q,则q是p的支配点,我们记除了该点自己外,所有支配点中到它最近的点为该点的最近支配点(记为idom),将所有点与它的最近支配点连边构成的树就是支配树。

求法

对于DAG,非常简单,建出来的支配树也就是灭绝树
首先建出dfs树,求出每个点的dfs序,之后点的大小比较都根据dfs序。
对于一般流程图则相对复杂,首先定义一下半支配点(记为sdom):
sdom(w)=min{v|∃(v0,v1,⋯,vk−1,vk),v0=v,vk=w,∀1≤i≤k−1,vi>w}
从这个定义不难发现,半支配点要么与该点直接相连,要么通过dfs序大的点绕到该点。

经过思考也不难得到下面的定理:
对于w≠r,令u为v到w的路径上(包含v不包含w,v>w)中sdom(u)最小的一个点,有
若sdom(w)=sdom(u),则idom(w)=sdom(w),反之idom(w)=iodm(u).

对于u点,只要用带权并查集维护一下即可,这样按照dfs序从大到小便可一次求出他们的半支配点,并且顺便求出sdom(w)=sdom(u)时的idom,最后在按dfs序顺次扫一遍,即可求出所有点的支配点。

例题(HDU4694 Important Sisters)

题意

给出一张有向图,求出以n为源点时,所有点的支配点的编号之和。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define N 50010
#define M 100100
using namespace std;

int n,m,first[N],ans[N],bb,fa[N],fb[N],mn[N],dfn[N],tt,sdo[N],ido[N];
bool vis[N];
struct Bn
{
    int to,next;
}bn[M];
vector<int>pre[N],have[N];

inline void add(int u,int v)
{
    bb++;
    bn[bb].to=v;
    bn[bb].next=first[u];
    pre[v].push_back(u);
    first[u]=bb;
}

int ff(int u)
{
    if(u==fb[u]) return u;
    int res=ff(fb[u]);
    if(sdo[mn[fb[u]]]<sdo[mn[u]]) mn[u]=mn[fb[u]];
    return fb[u]=res;
}

inline int ask(int u)
{
    ff(u);
    return mn[u];
}

void dfs(int now)
{
    int p,q;
    vis[now]=1;
    dfn[++tt]=now;
    sdo[now]=tt;
    for(p=first[now];p!=-1;p=bn[p].next)
    {
        if(vis[bn[p].to]) continue;
        fa[bn[p].to]=now;
        dfs(bn[p].to);
    }
}

int main()
{
    int i,j,k,p,q;
    while(~scanf("%d%d",&n,&m))
    {
        tt=bb=0;
        memset(first,-1,sizeof(first));
        memset(vis,0,sizeof(vis));
        for(i=1;i<=m;i++)
        {
            scanf("%d%d",&p,&q);
            add(p,q);
        }
        for(i=1;i<=n;i++) fb[i]=mn[i]=ans[i]=i;
        dfs(n);
        for(j=tt;j>=2;j--)
        {
            i=dfn[j];
            for(k=0;k<pre[i].size();k++)
                if(vis[pre[i][k]])
                    sdo[i]=min(sdo[i],sdo[ask(pre[i][k])]);
            have[dfn[sdo[i]]].push_back(i);
            fb[i]=fa[i];
            for(k=0;k<have[fa[i]].size();k++)
            {
                p=have[fa[i]][k];
                q=ask(p);
                if(sdo[p]==sdo[q]) ido[p]=fa[i];
                else ido[p]=q;
            }
            have[fa[i]].clear();
        }
        for(j=2;j<=tt;j++)//注意是从2开始
        {
            i=dfn[j];
            if(ido[i]!=dfn[sdo[i]]) ido[i]=ido[ido[i]];
            ans[i]+=ans[ido[i]];
        }
        for(i=1;i<n;i++)
        {
            if(vis[i]) printf("%d ",ans[i]);
            else printf("0 ");
        }
        printf("%d\n",n);
        for(i=1;i<=n;i++) pre[i].clear();
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值