hdu4694 Important Sisters 支配树

2 篇文章 0 订阅

Problem Description


这里写图片描述
There are N clones of Misaka Mikoto (sisters) forming the Misaka network. Some pairs of sisters are connected so that one of them can pass message to the other one. The sister with serial number N is the source of all messages. All the other sisters get message directly or indirectly from her. There might be more than one path from sister #N to sister #I, but some sisters do appear in all of these paths. These sisters are called important sister of sister #K. What are the important sisters of each sister?

1 ≤ N ≤ 50,000
0 ≤ M ≤ 100,000

Solution


本蒟蒻的第一道支配树,markmark

所谓支配树就是我们对于一个有向图,求出点x从给定起点st到x的必经点idom(x),并连边(idom(x),x)。由此得到的树形图称为支配树
对于树上的点我们可以直接求支配点,显然为它的父亲
对于一般有向图上的点我们可以考虑用tarjan老爷子的一种O((n+m)α)做法

首先我们求出原图的dfs树,并记下每个点x在dfs序中的位置dfn[x],记下以x为终点的边集pre[x]
定义idom(x)为点x的支配点,定义sdom(x)为点x的半支配点
所谓半支配点即是:存在一条从sdom(x)出发的路径到x,且路径上任意节点q(除去x和sdom(x)),都满足dfn[q]>dfn[x]的dfs序最小的点(绕

考虑按照dfs序倒着做。假设我们处理到dfs序中第i个节点编号为x,那么sdom(x)有两种转移:
1. sdom(x)=y (y与x直接相连且dfn[y]< dfn[x])
2. sdom(x)=min{sdom(z)} (z在y到根的路径上且dfn[y]>dfn[x])
感觉这个比较显然。一个点的sdom可以是直接走来,也可以是走到别处再绕来

现在考虑怎么用sdom求idom。还是分两类情况
这里写图片描述
在这个图里sdom(4)=2,sdom(3)=1
我们发现在sdom(4)到4的链上存在一个点3,有dfn[sdom(3)]< dfn[sdom(4)]
在这种情况下,记idom(4)=idom(3)。由于我们是倒序做的,此时还不知道idom(3),因此需要做完之后扫一遍求

这里写图片描述
对于这种情况,我们发现sdom(5)=2,在sdom(5)到5这条链上没有节点的sdom到了sdom(5)的上面,此时idom(5)=sdom(5)。这个可以感受一下正确性

求idom的具体证明非常麻烦且绕口,此处略去不讲

谈具体实现:
1. 我们需要写一个并查集维护链上最小值,兹磁路径压缩
2. 对于pre可以开vector来存,这样不用建两个图
3. 需要时刻注意点的编号及dfs序的区别,这个搞错了还不好调囧rz

具体到这题,我们建出御坂网络的支配树,然后在树上统计答案即可
需要注意的是dfs序的长度不一定等于n,可能存在有的点无法从某个起点访问到

Code


#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <vector>
#define rep(i,st,ed) for (int i=st,_=ed;i<=_;++i)
#define drp(i,st,ed) for (int i=st,_=ed;i>=_;--i)
#define fill(x,t) memset(x,t,sizeof(x))

typedef long long LL;
const int N=50005;
const int E=100005;

std:: vector <int> pre[N],dom[N];

struct edge {int y,next;} e[E];

int fa[N],acs[N],mn[N],id[N],dfn[N];
int ls[N],idom[N],sdom[N],edCnt;
LL f[N];

int read() {
    int x=0,v=1; char ch=getchar();
    for (;ch<'0'||ch>'9';v=(ch=='-')?(-1):(v),ch=getchar());
    for (;ch<='9'&&ch>='0';x=x*10+ch-'0',ch=getchar());
    return x*v;
}

void add_edge(int x,int y) {
    e[++edCnt]=(edge) {y,ls[x]}; ls[x]=edCnt;
}

void dfs(int now) {
    dfn[now]=++dfn[0];
    id[dfn[0]]=now;
    for (int i=ls[now];i;i=e[i].next) {
        pre[e[i].y].push_back(now);
        if (!dfn[e[i].y]) {
            fa[e[i].y]=now;
            dfs(e[i].y);
        }
    }
}

int find(int x) {
    if (acs[x]==x) return x;
    int tmp=find(acs[x]);
    if (dfn[sdom[mn[acs[x]]]]<dfn[sdom[mn[x]]]) mn[x]=mn[acs[x]];
    return acs[x]=tmp;
}

int smin(int x,int y) {
    return (dfn[x]<dfn[y])?x:y;
}

void build(int n) {
    drp(i,dfn[0],1) {
        int x=id[i];
        if (!pre[x].empty()) {
            for (int j=0;j<pre[x].size();j++) {
                int y=pre[x][j];
                if (dfn[y]<dfn[x]) sdom[x]=smin(sdom[x],y);
                else {
                    find(y);
                    sdom[x]=smin(sdom[x],sdom[mn[y]]);
                }
            }
            pre[x].clear();
        }
        acs[x]=fa[x];
        dom[sdom[x]].push_back(x);
        if (!dom[fa[x]].empty()) {
            for (int j=0;j<dom[fa[x]].size();j++) {
                int y=dom[fa[x]][j];
                find(y); int d=mn[y];
                if (dfn[sdom[d]]>=dfn[sdom[y]]) idom[y]=sdom[y];
                else idom[y]=d;
            }
            dom[fa[x]].clear();
        }
    }
    rep(i,1,dfn[0]) {
        int x=id[i];
        if (idom[x]!=sdom[x]) idom[x]=idom[idom[x]];
        f[x]=f[idom[x]]+x;
    }
    rep(i,1,n-1) printf("%lld ", f[i]);
    printf("%lld\n", f[n]);
}

int main(void) {
    for (int n,m;scanf("%d%d",&n,&m)!=EOF;) {
        fill(dfn,0);
        fill(ls,0); edCnt=0;
        rep(i,1,m) {
            int x=read(),y=read();
            add_edge(x,y);
        }
        rep(i,1,n) {
            idom[i]=f[i]=0;
            sdom[i]=acs[i]=mn[i]=i;
        }
        dfs(n); build(n);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值