【学习笔记】DAG / 一般有向图的支配树 / 灭绝树

定义与声明

一个有向图 G G G。给定一个起点 s s s,假设 s s s 能到达所有点。

若去掉某个点 i i i 后, s s s 无法到达 j j j,则称 i i i j j j 的支配点。

显然支配点存在传递关系。

s s s 为根,使得对于任意节点 i i i,其树上祖先均为 i i i 的支配点,这样的树称之为支配树。(有的人叫做灭绝树)

任一有向图都存在支配树,前提是满足 s s s 能到达所有点。

支配树不一定是图 G G G 的树形图,即树上有些边不存在于图 G G G 中。


  • dfs \text{dfs} dfs 树。

对图 G G G dfs \text{dfs} dfs 树。每个点有一个 dfn \text{dfn} dfn 序,即 dfs \text{dfs} dfs 的时间戳。

dfs \text{dfs} dfs 树中的边称为树边,除此之外的原图中的边称为非树边。

非树边又分为前向边,返祖边,横叉边。前向边是 dfn \text{dfn} dfn 序小的连向大的,返祖边和横叉边则相反。

  • 最近支配点 idom \text{idom} idom

i i i 的支配点中 dfn \text{dfn} dfn 序最大的点,即支配树上 i i i 的父亲。

注意:支配树上的父亲不一定就是 dfs \text{dfs} dfs 树上的父亲。

显然,除 s s s 以外的所有点均有唯一的 idom \text{idom} idom

  • 半支配点 sdom \text{sdom} sdom

v v v 的半支配点 u u u 是满足『 G G G 中存在一条从点 u u u 到点 v v v 的路径,且路径上经过点的 dfn > dfn [ v ] \text{dfn}>\text{dfn}[v] dfn>dfn[v](不包含 u , v u,v u,v)』的 dfn \text{dfn} dfn 最小的点。

u u u 只走非树边能到达 v v v dfn \text{dfn} dfn 最小的 u u u

特别的,如果 u u u 有一条边直接连向 v v v,则也是 sdom ( v ) \text{sdom}(v) sdom(v) 的候选点,这相当于路径上没有其他点。

u → v : u u\rightarrow v:u uv:u 存在一条直接连向 v v v 的边。

u ⇝ ^ v : u\hat\leadsto v: u^v: 存在一条由树边组成的 u u u v v v 的路径,且 u ≠ v u\ne v u=v

u ⇝ ¨ v : u\ddot\leadsto v: u¨v: 存在一条由树边组成的 u u u v v v 的路径,允许 u = v u=v u=v

注意:下面引理和定理的树均指 dfs \text{dfs} dfs 树,而非支配树。树边/非树边也是在 dfs \text{dfs} dfs 树的基础上定义的。


引理与定理

  • 引理Ⅰ: ∀ i ≠ s \forall_{i\ne s} i=s idom ( i ) ⇝ ^ i \text{idom}(i)\hat\leadsto i idom(i)^i idom ( i ) \text{idom}(i) idom(i) 一定是 dfs \text{dfs} dfs 树上 i i i 的某个祖先点。

    显然。如果不是,那么去掉 idom(i) \text{idom(i)} idom(i) 后, s s s 可以通过走树边访问到 i i i。这就不满足『支配 』的定义了。

  • 引理Ⅱ: ∀ i ≠ s \forall_{i\ne s} i=s sdom ( i ) ⇝ ^ i \text{sdom}(i)\hat\leadsto i sdom(i)^i sdom(i) \text{sdom(i)} sdom(i) 一定是 dfs \text{dfs} dfs 树上 i i i 的某个祖先点。

    假设 sdom ( i ) \text{sdom}(i) sdom(i) 不是 i i i 的祖先,那么我们可以从 sdom ( i ) \text{sdom}(i) sdom(i) 开始沿着树边往上走,直到走到 i i i 的某个祖先点 x x x 上。

    这期间肯定不会经过除 x x x 外的任何一个 i i i 的祖先点。

    显然 x x x 更符合 sdom \text{sdom} sdom 的条件。

  • 引理Ⅲ: ∀ i ≠ s \forall_{i\ne s} i=s idom ( i ) ⇝ ¨ sdom ( i ) \text{idom}(i)\ddot\leadsto\text{sdom}(i) idom(i)¨sdom(i) idom ( i ) \text{idom}(i) idom(i) 要么是 sdom ( i ) \text{sdom}(i) sdom(i),要么是 sdom ( i ) \text{sdom}(i) sdom(i) 的祖先。

    通过前面的引理,我们知道 idom ( i ) , sdom ( i ) \text{idom}(i),\text{sdom}(i) idom(i),sdom(i) 一定都在支配树中 i i i 到根的路径上,即是 i i i 的某个祖先点。

    所以引理如果不成立,我们就可以从 s s s 直接走到 sdom(i) \text{sdom(i)} sdom(i) 然后走到 i i i,且没有经过 idom ( i ) \text{idom}(i) idom(i)

    这显然不符合 idom ( i ) \text{idom}(i) idom(i) 的定义。所以引理一定成立。

  • 引理Ⅳ: ∀   u ⇝ ¨ v \forall\ u\ddot\leadsto v  u¨v ,有 u ⇝ ¨ idom ( v ) u\ddot\leadsto \text{idom}(v) u¨idom(v) idom ( v ) ⇝ ¨ idom ( u ) \text{idom}(v)\ddot\leadsto\text{idom}(u) idom(v)¨idom(u)

    u , v , idom ( u ) , idom ( v ) u,v,\text{idom}(u),\text{idom}(v) u,v,idom(u),idom(v) 都在根到某个叶子节点的路径上。

    引理也就是说这两条 idom ( x ) \text{idom}(x) idom(x) x x x 的路径完全不交或包含。

    分情况讨论:

    • dfn [ u ] ≤ dfn [ idom ( v ) ] \text{dfn}[u]\le \text{dfn}[\text{idom}(v)] dfn[u]dfn[idom(v)]

      此时有 u ⇝ ¨ idom(v) ⇝ ¨ v u\ddot\leadsto \text{idom(v)}\ddot\leadsto v u¨idom(v)¨v。完全不交。

    • dfn [ u ] > dfn [ idom ( v ) ] \text{dfn}[u]>\text{dfn}[\text{idom}(v)] dfn[u]>dfn[idom(v)]

      此时有 idom ( v ) ⇝ ¨ u \text{idom}(v)\ddot\leadsto u idom(v)¨u

      然后要么有 idom ( u ) ⇝ ¨ idom ( v ) \text{idom}(u)\ddot\leadsto \text{idom}(v) idom(u)¨idom(v),要么有 idom ( v ) ⇝ ¨ idom ( u ) \text{idom}(v)\ddot\leadsto \text{idom}(u) idom(v)¨idom(u)

      如果 idom ( u ) ⇝ ¨ idom ( v ) \text{idom}(u)\ddot\leadsto \text{idom}(v) idom(u)¨idom(v),那么去掉 idom ( v ) \text{idom}(v) idom(v) idom ( u ) \text{idom}(u) idom(u) 一定还能到达 u u u,否则 idom(u) \text{idom(u)} idom(u) 就不是 u u u 的支配点,而 idom ( v ) \text{idom}(v) idom(v) 才是了。

      所以也一定能到达 v v v。这样又不符合 idom ( v ) \text{idom}(v) idom(v) 的定义了。矛盾。

      所以有 idom ( v ) ⇝ ¨ idom ( u ) \text{idom}(v)\ddot\leadsto \text{idom}(u) idom(v)¨idom(u)。包含。

  • 定理Ⅰ: ∀   u ≠ s \forall\ u\ne s  u=s,如果 sdom ( u ) ⇝ ^ v ⇝ ¨ u   ∧  dfn [ sdom ( v ) ] ≥ dfn [ sdom ( u ) ] \text{sdom}(u)\hat\leadsto v\ddot\leadsto u\ \wedge\ \text{dfn}[\text{sdom}(v)]\ge\text{dfn}[\text{sdom}(u)] sdom(u)^v¨u  dfn[sdom(v)]dfn[sdom(u)],则有 idom ( u ) = sdom ( u ) \text{idom}(u)=\text{sdom}(u) idom(u)=sdom(u)

    前提条件意思就是:

    对于所有满足 sdom ( u ) \text{sdom}(u) sdom(u) v v v 祖先, v v v u u u 祖先(可以相等)的 v v v,均满足 sdom(u) \text{sdom(u)} sdom(u) u u u 的路径完全包含 sdom ( v ) \text{sdom}(v) sdom(v) v v v 的路径。

  • 定理Ⅱ: ∀   u ≠ s \forall\ u\ne s  u=s,如果 sdom ( u ) ⇝ ^ v ⇝ ¨ u \text{sdom}(u)\hat\leadsto v\ddot\leadsto u sdom(u)^v¨u,假设 v v v dfn [ sdom(v) ] \text{dfn}[\text{sdom(v)}] dfn[sdom(v)] 最小的点,

    且如果 dfn [ sdom ( v ) ] < dfn [ sdom ( u ) ] \text{dfn}[\text{sdom}(v)]<\text{dfn}[\text{sdom}(u)] dfn[sdom(v)]<dfn[sdom(u)],则有 idom ( u ) = idom(v) \text{idom}(u)=\text{idom(v)} idom(u)=idom(v)

    • 结合以上两个定理有,推论Ⅰ: ∀   u ≠ s \forall\ u\ne s  u=s,令 u u u 为所有满足 sdom ( v ) ⇝ ^ u ⇝ ¨ v \text{sdom}(v)\hat\leadsto u\ddot\leadsto v sdom(v)^u¨v u u u dfn [ sdom ( u ) ] \text{dfn}[\text{sdom}(u)] dfn[sdom(u)] 最小的点。

      idom ( v ) = { sdom ( v ) sdom ( u ) = sdom ( v ) idom ( u ) sdom ( u ) < sdom ( v ) \text{idom}(v)=\begin{cases}\text{sdom}(v)&\text{sdom}(u)=\text{sdom}(v)\\\text{idom}(u)&\text{sdom}(u)<\text{sdom}(v)\end{cases} idom(v)={sdom(v)idom(u)sdom(u)=sdom(v)sdom(u)<sdom(v)

  • 定理Ⅲ: ∀   u ≠ s \forall\ u\ne s  u=s,有 sdom ( u ) = min ⁡ { v ∣ ( v , u ) ∈ E   ,   v < u } \text{sdom}(u)=\min\Big\{v\mid (v,u)\in E\ ,\ vsdom(u)=min{v(v,u)E , v<u}

    sdom ( u ) \text{sdom}(u) sdom(u) 的候选点只用考虑两类:

    • 由树边或者前向边直接连过来的点。
    • dfn \text{dfn} dfn 更大的且与 u u u 有边相连的点及其祖先的 sdom \text{sdom} sdom。这又可以分为两类:
      • dfn [ v ] > dfn [ u ] , ∃ ( v , u ) ∈ E \text{dfn}[v]>\text{dfn}[u],\exist(v,u)\in E dfn[v]>dfn[u],(v,u)E,则 sdom ( v ) \text{sdom}(v) sdom(v) 一定是 sdom ( u ) \text{sdom}(u) sdom(u) 的一个候选点。
      • 满足 dfn [ v ] > dfn [ u ] , ∃ ( v , u ) ∈ E \text{dfn}[v]>\text{dfn}[u],\exist(v,u)\in E dfn[v]>dfn[u],(v,u)E v v v 的部分祖先,且这些祖先的 dfn > dfn [ u ] \text{dfn}>\text{dfn}[u] dfn>dfn[u],则这些祖先的 sdom \text{sdom} sdom 也是候选点。

定理Ⅰ,Ⅱ实在不会证明,感性理解好了,直接开始摆烂

大本钟下寄快递,上面开摆下面寄


算法步骤

建立支配树的算法步骤:

  • 如果对于一棵树,树根为 s s s,那么这棵树本身就是一棵支配树。

  • 如果是一个 DAG \text{DAG} DAG ,则可以拓扑排序。然后依次确定每个点的 idom \text{idom} idom

    设当前点为 i i i,有 j → i j\rightarrow i ji,则所有 j j j 在支配树中的 LCA \text{LCA} LCA 就是 idom ( i ) \text{idom}(i) idom(i)

    拓扑 + + +倍增时间大概 O ( ( n + m ) log ⁡ n ) O((n+m)\log n) O((n+m)logn)

  • 如果是一般图问题,则可以先求出每个点的 sdom \text{sdom} sdom,然后对所有点,连边 ( sdom ( i ) , i ) (\text{sdom}(i),i) (sdom(i),i),去掉非树边,就形成了 DAG \text{DAG} DAG。支配关系与原图一致。


代码实现

DAG \text{DAG} DAG 模板题:ZJOI2012 灾难

代码实现:

#include 
using namespace std;
#define maxn 66000
int dep[maxn], deg[maxn], f[maxn][20], siz[maxn];
vector < int > pre[maxn], G[maxn], rG[maxn];
queue < int > q;
int n;

void dfs( int u, int fa ) {
    siz[u] = 1;
    for( int v : rG[u] ) if( v ^ fa ) dfs( v, u ), siz[u] += siz[v];
}

int LCA( int u, int v ) {
    if( dep[u] < dep[v] ) swap( u, v );
    for( int i = 19;~ i;i -- ) if( dep[f[u][i]] >= dep[v] ) u = f[u][i];
    if( u == v ) return u;
    for( int i = 19;~ i;i -- ) if( f[u][i] ^ f[v][i] ) u = f[u][i], v = f[v][i];
    return f[u][0];
}

int main() {
    scanf( "%d", &n );
    for( int i = 1, x;i <= n;i ++ ) {
        while( ~ scanf( "%d", &x ) and x ) //G[x]:x有dfs树路径到的点的集合
            G[x].push_back( i ), deg[i] ++;
    }
    int root = 0; //新建大起点 即使其变成有根树
    //pre[i]:i的前驱集合 即从根开始有dfs树路径到i的点
    for( int i = 0;i <= n;i ++ )
        if( ! deg[i] ) pre[i].push_back( root ), q.push( i );
    while( ! q.empty() ) {
        int u = q.front(); q.pop();
        int lca = pre[u][0];
        for( int x : pre[u] ) lca = LCA( lca, x );
        dep[u] = dep[lca] + 1;
        f[u][0] = lca;//建立支配树上的关系
        for( int i = 1;i < 20;i ++ )
            f[u][i] = f[f[u][i - 1]][i - 1];
        rG[lca].push_back( u );
        for( int v : G[u] ) {
            pre[v].push_back( u );//u是v的前驱之一
            if( ! -- deg[v] ) q.push( v );
        }
    }
    dfs( root, -1 );
    for( int i = 1;i <= n;i ++ ) printf( "%d\n", siz[i] - 1 );
    return 0;
}

一般有向图模板题:luogu-P5180 【模板】支配树

代码实现:

#include 
#include 
#include 
#include 
using namespace std;
#define maxn 200005
struct node {
	vector < int > g[maxn];
	void AddEdge( int u, int v ) {
		g[u].push_back( v );
	}
}G, rG, Dfs, rDfs, dominate;//原图 反图 dfs树 反dfs树 支配树
queue < int > q;
int n, m, cnt;
int id[maxn], dfn[maxn], fa[maxn], anc[maxn], min_anc[maxn], sdom[maxn], d[maxn], dep[maxn], ans[maxn];
int f[maxn][20];
/*
Semi-domination半支配 表示v点的所有半支配点的最小的那个
半支配点:
存在从一个点u到v的路径中(不包括u,v),所有dfs树的点的dfn都大于v的dfn
如果u是v在dfs树上的父节点,那么u也是v的半支配点
*/

void dfs( int u ) {//寻找dfs树
	id[dfn[u] = ++ cnt] = u; //打时间戳
	for( int i = 0;i < G.g[u].size();i ++ ) {
		int v = G.g[u][i];
		if( dfn[v] ) continue; else;
		fa[v] = u;
		Dfs.AddEdge( u, v );
		dfs( v );
	}
}

int find( int x ) {
	if( x == anc[x] ) return x;
	int father = anc[x];
	anc[x] = find( anc[x] );
	if( dfn[sdom[min_anc[x]]] > dfn[sdom[min_anc[father]]] )
		min_anc[x] = min_anc[father];// min_anc表示x到sdom[x]路径上dfn[sdom]值最小的点
	return anc[x];
}

void build_dfs() {//建立与原图等价的DAG
	for( int i = 1;i <= n;i ++ )
		anc[i] = min_anc[i] = sdom[i] = i;
	for( int i = n;i > 1;i -- ) {
		int u = id[i];
		if( ! dfn[u] ) continue;
		int t = n;
		for( int j = 0;j < rG.g[u].size();j ++ ) {
			int v = rG.g[u][j];
			if( dfn[v] < dfn[u] )
				t = min( t, dfn[v] );
			else {
				find( v );
				t = min( t, dfn[sdom[min_anc[v]]] );
			}
		}
		sdom[u] = id[t];
		anc[u] = fa[u];
		Dfs.AddEdge( sdom[u], u );
	}
}

int lca( int u, int v ) {
	if( ! u || ! v ) return u + v;
	if( dep[u] < dep[v] ) swap( u, v );
	for( int i = 19;~ i;i -- )
		if( dep[f[u][i]] >= dep[v] ) u = f[u][i];
	if( u == v ) return u;
	for( int i = 19;~ i;i -- )
		if( f[u][i] != f[v][i] ) u = f[u][i], v = f[v][i];
	return f[u][0];
}

void build_dominate( int u ) {
	int t = 0;
	for( int i = 0;i < rDfs.g[u].size();i ++ ) {
		int v = rDfs.g[u][i];
		t = lca( t, v );
	}
	dep[u] = dep[t] + 1;
	dominate.AddEdge( t, u );
	f[u][0] = t;
	for( int i = 1;i < 20;i ++ )
		f[u][i] = f[f[u][i - 1]][i - 1];
}

void topo() {
	for( int i = 1;i <= n;i ++ )
		for( int j = 0;j < Dfs.g[i].size();j ++ ) {
			int k = Dfs.g[i][j];
			d[k] ++;
			rDfs.AddEdge( k, i );
		}
	for( int i = 1;i <= n;i ++ )
		if( ! d[i] ) {
			Dfs.AddEdge( 0, i );
			rDfs.AddEdge( i, 0 );
		}
	q.push( 0 );
	while( ! q.empty() ) {
		int u = q.front(); q.pop();
		for( int i = 0;i < Dfs.g[u].size();i ++ ) {
			int v = Dfs.g[u][i];
			if( -- d[v] <= 0 ) {
				build_dominate( v );
				q.push( v );
			}
		}
	}
}

void work( int u ) {
	ans[u] = 1;
	for( int i = 0;i < dominate.g[u].size();i ++ ) {
		int v = dominate.g[u][i];
		work( v );
		ans[u] += ans[v];
	}
}

int main() {
	scanf( "%d %d", &n, &m );
	for( int i = 1, u, v;i <= m;i ++ ) {
		scanf( "%d %d", &u, &v );
		G.AddEdge( u, v );
		rG.AddEdge( v, u );
	}
	dfs( 1 );
	build_dfs();
	topo();
	work( 0 );
	for( int i = 1;i <= n;i ++ )
		printf( "%d ", ans[i] );
	return 0;
}

你可能感兴趣的:(学习,深度优先,图论,支配树,DAG)