• 支配树学习笔记


    本文参考自cz_xuyixuan的支配树blog

    以下给出若干定义

    • 给定一张有向图,定义起点为 (r)
    • 定义 (x)(y) 的支配点,即 (x in dom(y)),当且仅当删掉 (x) 后, 从 (r) 无法到达 (y),且 (x != y)
    • 定义 (x)(y) 的最近支配点,当且仅当,(x in dom(y))(forall w in dom(y),w != x)(w in dom(x)),以下称为 (idom(y) = x),亦称为 (x)(y)最近支配点
    • 由定义可知,(idom(x))(x) 的连边可以构成一棵树,称为支配树
    • 定义有向图的 (dfs) 树为 (T)
    • 以下所有大小关系,均指 (dfn) 的大小关系,即重定义 (<) 表示 (dfn[x] < dfn[y].)
    • 定义 (x)(y) 的半支配点,即(sdom(y) = min{x|exist x -> v_1 -> v_2 -> ..... v_k ->y,forall i in [1,k],v_i > y}),用人话讲就是 (x) 为满足存在一条除首尾外,经过的点都大于 (y) 的到达 (y) 的路径的点中 (dfs) 序最小的点
    • 因为起点 (r) 的支配点不存在,所以以下讨论都默认不讨论 (r)
    • 以下用 (a ightarrow b)表示存在一条 (a)(b) 的路径,不经过树边,(a -> b)表示只经过树边, (a)(b) 的所有路径则统称为 ((a,b) in path)

    以下给出若干定理和引理

    • (idom(u)) 一定是 (u) 的祖先(1)

    显然

    • (idom(u))一定是 (sdom(u)) 的祖先(2)

    (proof) :考虑反证,若不是,则存在 (r -> sdom(u) ightarrow u)不经过 (idom(u)),矛盾

    • (sdom(u)) 一定是 (u) 的祖先(3)

    (proof) : 考虑反证,首先 (sdom(u)) 可以是 (fa(u)) ,所以 (sdom(u) leq fa(u) < u) ,若不是祖先,则必然是其他子树,考虑在 (dfs) 的过程中 ,若 ((sdom(u),u) in path), 那么在遍历到 (sdom(u)) 的过程中,必然直接遍历到 (u) ,与 (sdom(u) ightarrow u)矛盾

    定理 ( idom 与 sdom 关系定理)
    定义: (u)(sdom(w))(w) 的树的路径中(不包含 (sdom(w))), (sdom(u)) 最小的点,那么有

    • (idom(w) = sdom(u)) ((sdom(u) = sdom(w)))(4)
    • (idom(w) = idom(u)) ((sdom(u) < sdom(w)))(5)

    (proof):首先我们考虑对于任意一对 ((u,x)) 如何证明 (idom(u) = x),我们仅需证明两点

    • (x in dom(u))
    • (forall v in (x -> u), v != x) , 有(v otin dom(u))

    (4) 的证明

    (sdom(u) = sdom(w)),我们考虑 (sdom(u)) 必然是支配点,假设存在一个点 (x) 使得 (r -> x ightarrow o -> w), 且 (x < u),那么 (sdom(o) < sdom(u)),矛盾

    接着我们考虑假设有一个点 (y)(sdom(u) -> u)中,且是支配点,显然和引理((2))矛盾

    (5) 的证明

    先证(idom(u) in dom(w))

    考虑反证,若删掉 (idom(u)) 后,仍然存在 ((r,w) in path),那么必然是 (r -> x ightarrow o -> w),且 (o > u,x < idom(u)),且 (sdom(o) = x < sdom(u)),矛盾

    再证不存在更近的支配点

    依然考虑反证,假设存在这样的点,不妨设为 (y),我们考虑将其删去,继续分类讨论

    • (1),(y otin (u,w)) 那么因为 (idom(u)) 已经是最近的支配点,那么必然存在 ((r,u) in path),又因为存在 (u -> w),那么必然存在((r,w) in path),矛盾
    • (2),(y in (u,w)),(idom(w)) 一定是 (sdom(w))的祖先,矛盾

    那么我们根据这个定理,只需求出 (sdom) ,就可以在 (O(nlog n))的时间内,求出 (idom)

    考虑 (sdom) 怎么求

    定理 (sdom 的另一种简化定义)(6)

    • (S1(w) = {(v,w) in E,v < w})
    • (S2(w) = {sdom(u)|u > w,exist(v,w),(u -> v) in T })
    • (sdom(w) = min{v|v in S1(w) cup S2(w)})

    用人话说,就是我们考虑所有((v,w) in E),如果(v < w),那么(v)可以纳入侯选集合,否则,我们考虑其所有祖先(u)满足(u > w),(sdom(u))可以纳入侯选集合

    证明:(S1)的证明是显然的,我们考虑 (S2) 怎么证,其实也是显然的,我们考虑这么一条路径,(sdom(u) -> u -> v -> w),满足条件

    有了以上这些定理和引理,我们考虑具体的构造过程

    构造过程

    大体可以分成两部分。

    • 得到 (sdom) 数组
    • (sdom) 得到 (idom)

    以下内容基本参考 cz_xuyixuan 的 blog,先膜拜

    算法基本流程

    • 初始化、跑一遍 (DFS) 得到 (T).
    • 按标号从大到小求出 (sdom)
    • 求出所有能确定的 (idom), 剩下的点记录下和哪个点的 (idom) 是相同的
    • 按照标号从小到大再跑一次,得到所有点的 (idom)

    第一步显然

    第二步和第三步可以一起做,我们可以考虑维护一个带权并查集,每个点维护其到根 (sdom) 的最小值,按 (dfs) 序从大到小扫一遍,每当我们计算完 (w) ,我们可以寻找当前所有 (sdom(u) = fa(w)) 的点 (u) , 计算 (min{sdom(v)| v in (sdom(u)->u)}),这个可以用 ( extstyle vector) 然后清空掉,容易发现这样维护的只有当前一条链,然后我们可以通过定理 (4), (5) ,来得到 (idom(u) = sdom(u)) 或者 (idom(u) = idom(v)),不过我们现在可能还不知道 (idom(v)) , 所以先打个标记

    最后,从小到大扫一遍,得到 (idom) 数组

    代码如下:也就调了一百万年吧

    #include<bits/stdc++.h> 
    using namespace std;
    int read() {
    	char c = getchar();
    	int x = 0;
    	while(c < '0' || c > '9')	c = getchar();
    	while(c >= '0' && c <= '9')	x = x * 10 + c - 48,c = getchar();
    	return x;
    }
    const int _ = 5e5 + 7;
    int sdom[_],idom[_];
    vector<int>E[_];
    vector<int>O[_];
    vector<int>T[_];
    vector<int>G[_]; 
    int par[_];
    #define pb push_back
    int n,m;int siz[_]; 
    int dfn[_],dfncnt,isdfn[_];
    int fa[_];int mn[_];
    bool cmp(int u,int v) {
    	return dfn[u] < dfn[v];
    }
    int Min(int u,int v) {
    	if(cmp(u,v))	return u;
    	return v;
    }
    int get(int u) {
    	if (fa[u] == u)	return u;	
    	int tmp = fa[u];	
    	fa[u] = get(fa[u]);
    	if(dfn[sdom[mn[u]]] > dfn[sdom[mn[tmp]]])		mn[u] = mn[tmp];
    	return fa[u];
    }
    void dfs(int u) {
    	dfn[u] = ++dfncnt;isdfn[dfncnt] = u;
    	sdom[u] = u;
    	for (auto v:E[u]) {
    		if(!dfn[v]){
    //			cout<<"E"<<' '<<u <<' '<<v<<'
    ';
    			par[v] = u;
    			dfs(v);
    		}
    	}
    }
    void tree(int u) {
    	siz[u] = 1;
    	for (auto v:T[u]) {
    //		cout << u << ' ' <<v<<"hxsb"<<'
    ';
    		tree(v);
    		siz[u] += siz[v];
    	}
    }
    int main() {
    	n = read(),m = read();
    	for (int i = 1; i <= m; ++i) {
    		int u = read(),v = read();
    		E[u].pb(v);O[v].pb(u);
    	}
    	E[0].pb(1);O[1].pb(0);
    	dfs(0);
    	for (int i = 1; i <= n; ++i)	fa[i] = i;
    	for (int i = n + 1; i >= 1; --i) {
    		int u = isdfn[i];
    		for (auto v:O[u]) {
    			if (cmp(v,u)){
    				sdom[u] = Min(sdom[u],v);
    			}
    			else {
    				get(v);				
    				sdom[u] = Min(sdom[u],sdom[mn[v]]);
    			}
    		}
    		G[sdom[u]].pb(u);
    		mn[u] = u;
    		for (auto v:E[u]) {
    			if(dfn[v] > dfn[u] && par[v] == u) {
    				fa[v] = u;
    				get(v);
    			}
    		}
    		for (auto v:G[par[u]])  {
    //			if(v == 4)	cout<<<<'
    ';
    			get(v);
    			int w = mn[v];
    //			if(v == 4)	cout<<w<<'
    ';
    			if(sdom[w] == sdom[v])	idom[v] = sdom[v];
    			else	idom[v] = w;
    		}
    		get(u);
    		G[par[u]].clear();
    	}
    	for (int i = 1; i <= n + 1; ++i) {
    		int u = isdfn[i];
    		if (idom[u] == sdom[u])	continue;
    		else	idom[u] = idom[idom[u]];
    	}		
    //	for (int i = 1; i <= n; ++i)	cout<<idom[i]<<' ';
    //	cout<<'
    ';
    	for (int i = 1; i <= n; ++i)	T[idom[i]].pb(i);
    	tree(0);
    	for (int i = 1; i <= n; ++i)	printf("%d ",siz[i]);
    	cout<<'
    ';
    	return 0;
    }
    

    一些习题:

    [省选联考 2021 A 卷] 支配
    感觉是模板题,不建议做

  • 相关阅读:
    python---1
    20190802—list、range、extend函数
    20190802—def定义函数
    20190802—import函数调用
    如何在EXCEL中将多个单元格内容合并到一个单元格中
    20190619—return函数的用法
    20190618—位置参数、默认参数、不定长参数
    excel 怎么计算单元格个数
    20190616——and和or使用方法、python运算符总结、python数据类型
    20190616——enumerate的用法
  • 原文地址:https://www.cnblogs.com/y-dove/p/14876624.html
Copyright © 2020-2023  润新知