支配树 !!!
这是今天小白讲的内容,于是晚上用支配树过了一道裸题(hdu4694)。
这里大概梳理一下。
我们有一个有向图,可以有环,然后给定一个起点 s 。
定义一个点 u 的支配集为 s 到 u 的每一条路径经过的点的交集,即 s 到 u 必经的点集。不过为了方便,以下讨论中点 u 的支配集均不包含 u 本身。
定义点 u 的支配点为点 u 的支配集中,离 u 最“近”的点,也就是说删掉点 u 的支配点过后,s 仍然可以到达 u 的支配集中别的点。
我们的目标就是要求出每个点的支配点。
dfn表示时间戳。首先这里有一个性质, DFS 有向图过后,若 dfn[u] < dfn[v],且 u到 v 有边, 那么在 DFS 树中 u 一定是 v 的祖先。 原因是有向图的 DFS 树中不存在从 dfn 小的点到 dfn 大的点的横叉边。
对于点 u,所有能够通过一些点 i 满足 dfn[i] > dfn[u] (不包括起点和终点)到达点 u 的点中, dfn 值最小的点为点 u 的半支配点(semi) 。即可以不经过点 u 的祖先到达点 u 的点中,dfn 最小的点。注意这个点一定是点 u 的祖先,这也是很容易就可以证明的。
半支配点看起来很像支配点,但实际上并不一定是。
比如这张图中,X 的半支配点就并不是 X 的支配点。(图片引自某博客)
不过没有关系,我们先考虑怎么求出半支配点。
对于一个点 u,若存在一条边 v -> u,这时我们考虑更新 u 的 semi。分两种情况,若 dfn[v] < dfn[u],说明 v 是 u 的祖先,直接用 v 更新 ;若 dfn[v] > dfn[u],那么这是一条横叉边,也就是说我们可以先走到点 v,再走到点 u,所以,这时我们应该用 v 和 v 的祖先中,不是 u 的祖先的点的 semi 来更新,因为我们可以从这个 semi 走到点 v 的祖先再走到 v 再跨过这条横叉边到 u。
带权并查集维护一下即可。
再考虑我们求出 semi 后,如何求出支配点,以下称之为 idom。
唯一有可能导致出现问题的就是上面的图中那种交叉的情况。于是我们对于点 u,令 u 的 semi 为 t,考虑点 u 到 u 的 t 这条路径上(不含点 t)的点中,令点 v 是 semi 的 dfn 最小的点,若 v 的 semi 等于 t,说明没有出现交叉,于是 u 的 idom 即为 t ; 但是若 v 的 semi 不等于 t,那么就出现了交叉,此时点 u 的 idom 应该等于点 v 的 idom。
同样是用带权并查集维护。
至此,问题已经得到解决。
关于实现方面,我们应该按照 dfn 从大到小的顺序处理每一个点。并且注意求 semi 和求 idom 是可以写在一起的,而且带权并查集维护的东西也是一样的。代码也只有不到一百行。
hdu4694:
#include
#include
#include
#include
#include
#define my_min(a,b) ((a)<(b)?(a):(b))
#define my_max(a,b) ((a)>(b)?(a):(b))
using namespace std;
int reads(int &x)
{
bool f=false ; char t=(char)getchar() ; x=0 ;
while(t<'0'||t>'9'){
if(t=='-')f=true ;
if(t==EOF)return EOF;
t=(char)getchar() ;
}
while(t>='0'&&t<='9'){
x=(x<<1)+(x<<3)+t-'0' ;
t=(char)getchar() ;
}
if(f)x=-x ;
return 0 ;
}
int n , m , fa[50055] , val[50055] , dfn[50055] , TTT , q[50055] , qnum , semi[50055] , idom[50055] , ans[50055] ;
int ff[50055] ;
vector G1[50055] , G2[50055] , tag[50055] , G3[50055] ;
// val 存的是 semi 最浅的点的编号
void init(){
TTT=qnum=0 ;
for(int i=1;i<=n;++i)fa[i]=i , G1[i].clear() , G2[i].clear() , G3[i].clear() , tag[i].clear() , semi[i]=i , ans[i]=idom[i]=val[i]=0 ;
memset(dfn,0,sizeof(int)*(n+5)) ;
dfn[0]=(1<<30)-1 ;
//
}
inline void update_1(int &a,int b){ if(dfn[b]
inline void update_2(int &a,int b){ if(dfn[semi[b]]
int find_fa(int a)
{
if(fa[a]==a)return fa[a] ;
int tf=fa[a] ;
fa[a]=find_fa(fa[a]) ;
update_2(val[a],val[tf]) ;
return fa[a] ;
}
void dfs(int a,int f){
dfn[a]=++TTT , q[++qnum]=a , ff[a]=f ;
int len=(int)G1[a].size() ;
for(int i=0;i
int b=G1[a][i] ;
if(!dfn[b])dfs(b,a) ;
}
}
void get_idom(){
for(int i=qnum;i>=1;--i){
int a=q[i] , len=(int)G2[a].size() ;
for(int j=0;j
int b=G2[a][j] ;
if(!dfn[b])continue ;
if(dfn[b]
else find_fa(b) , update_1(semi[a],semi[val[b]]) ;
}
len=(int)tag[a].size() ;
for(int j=0;j
int b=tag[a][j] , t;
find_fa(b) , t=semi[val[b]] ;
if(t==a)idom[b]=a ;
else idom[b]=-val[b] ; // (debug) 这里不是 -t
}
if(i!=1) tag[semi[a]].push_back(a) ;
if(ff[a])fa[a]=ff[a] , val[a]=a ;
}
for(int i=2;i<=qnum;++i){
int a=q[i] ;
if(idom[a]<0)idom[a]=idom[-idom[a]] ;
}
}
void dfs_2(int a,int now){
int len=(int)G3[a].size() ;
ans[a]=now ;
for(int i=0;i
int b=G3[a][i] ;
dfs_2(b,now+b) ;
}
}
void solve(){
for(int i=1;i<=m;++i){
int a,b;
scanf("%d%d",&a,&b) ;
G1[a].push_back(b) , G2[b].push_back(a) ;
}
dfs(n,0) ;
get_idom() ;
for(int i=1;i<=n;++i)if(idom[i])G3[idom[i]].push_back(i) ;
dfs_2(n,n) ;
for(int i=1;i<=n;++i) printf((i==n?"%d\n":"%d "),ans[i]) ;
}
int main()
{
while(reads(n)!=EOF){
reads(m) ;
init() ;
solve() ;
}
return 0 ;
}
评论(2)