支配树口胡

感性理解支配树

0. 前言

  • 代码都在最后

upd 2022/2/23. 增加一道例题(小W与施工),加上算法名(鬼知道我为啥没加算法名)

1. 支配树简介

支配树的定义:

对于一张有向图 \(\mathrm G\),指定一起点 \(s\) .(或许也被称作流程图?)

\(u\) 支配点 \(v\)(即 \(u\)\(v\)支配点)当且仅当 \(s\) 到达 \(v\) 的所有路径都必须通过 \(u\)(这意味着如果去掉点 \(\pmb u\) 则从 \(\pmb s\) 不可能到达 \(\pmb v\)

将所有点 \(u\) 向其最近支配点(其定义在后面)连边,不难发现,会得到一棵树,这棵树就叫做图 \(\mathrm G\) 的支配树(Dominator Tree).

2. 支配树的求法

2.1. 树的支配树

即为其本身(

2.2. DAG 的支配树

题目:[ZJOI2012]灾难

由于不吃任何东西的生物可能有多种,先加一个虚拟节点连向所有不吃东西的生物 .

不难发现,\(u\) 灭绝当且仅当 \(u\) 的所有直接食物的 LCA 灭绝 .

按支配树定义,把 \(u\) 的所有直接食物的 LCA 和 \(u\) 连边,支配树就建好力 .

只需按拓扑序依次加边,即可建成支配树 .

不难发现,对于生物 \(u\)\(u\) 在支配树上的子树大小即为题目要求的答案 .

3.3. 任意有向图的支配树

声明:内容都是感性理解,想看详细证明可见:https://www.cnblogs.com/meowww/archive/2017/02/27/6475952.html

记号声明:

  • \(\operatorname{dfn}(u)\)\(u\) 的图的 DFS 序中 \(u\) 的编号 .
  • \(u\mathrm{\, dom\,}v\) 表示 \(u\)\(v\) 的支配点 .
  • \(\operatorname{idom}(u)\)\(u\) 的最近支配点 .
  • \(\operatorname{sdom}(u)\)\(u\) 的半支配点 .

先放上 \(\operatorname{idom}\)\(\operatorname{sdom}\) 的定义:

最近支配点(immediate dominator)的定义:

在支配 \(u\) 的点中,如果一个支配点 \(v\neq u\) 满足 \(v\)\(u\) 剩下的所有非平凡支配点(即不是自己或起点的支配点)支配,则这个 \(v\) 称作 \(u\)最近支配点,记作 \(\operatorname{idom}(u)\) .

不难发现,最近支配点唯一 .

半支配点(semi-dominator)的定义:

\(u\) 的祖先中,通过非树枝边可以到打 \(u\) 深度最小的祖先,称作 \(u\) 的半必经点,记做 \(\operatorname{sdom}(u)\)(也可记 \(\operatorname{semi}(u)\)).

半支配点显然唯一 .


算法流程:

首先,求出原图的随便一棵 DFS 树,由此我们可以把图上的边分为四类:

  • 树枝边:在 DFS 树上的边(其余边称作非树枝边).
  • 前向边:从 DFS 树上的祖先指向后代 .
  • 后向边:从 DFS 树上的后代指向祖先 .
  • 横叉边:从一棵子树内指向另一棵子树内 .

先放上一定理:

对于 \(u\neq S\)\(S\) 是起点),\(v\) 为所有满足 \(\operatorname{sdom}(u)\) 可通过一条树枝边到达 \(v\)\(v\) 又可通过一条树枝边到达 \(u\),且 \(\operatorname{sdom}(u)\neq v\)

\[\large\operatorname{idom}(u)=\begin{cases}\operatorname{sdom}(u)&\operatorname{sdom}(u)=\operatorname{sdom}(v)\\\operatorname{idom}(v)&\operatorname{sdom}(u)\neq\operatorname{sdom}(v)\end{cases} \]

证明可以感性理解,即为按最近支配点是否可以绕过去到达 \(u\) 点分类讨论 .

从这个定理至少可以看出,求出每个点的 \(\operatorname{sdom}\) 就可求出每个点的 \(\operatorname{idom}\) .

那么 \(\operatorname{sdom}\) 怎么求呢?

我们有

对于任意 \(w\neq S\)

\[\large \begin{aligned}\operatorname{sdom}(w)=\min\{\{v\mid (v,w)\in E,\, v<w\}\cup\{\operatorname{sdom}(u)\mid (v,w)\in E,\,u>w,\,t(u,v)\}\}\end{aligned} \]

其中 \(t(u,v)\) 指在 DFS 树上 \(u\)\(v\) 可通过一条树枝边互通 .

这个也可以感性理解,前面那个集合是后向边和横叉边,后面那个集合是前向边和树枝边 .

根据上面那个东西就能轻♪松求出 \(\operatorname{sdom}\)

代码也很好写,用带权并查集可以轻松维护最小值 .

这个玩意就叫做 Lengauer-Tarjan 算法

2. 题

题倒是蛮少的,出出来也是模板

感觉出题空间挺大吧

1. Codeforces 757F

题意:略

DAG 上支配树板子

2. Codechef GRAPHCNT

题意:

问有多少个点对 \((x,y)\),满足存在路径 \(1\to x\)\(1\to y\) 满足 两条路径公共点只有点 \(1\)

就是支配树上 LCA 为 \(1\) 的点对,随便统计下就可 .

3. 小W与施工

题意:

一个无环流程图(含起点 \(s\),终点 \(t\) 的 DAG),\(q\) 次询问,每次去掉一条边,问 \(s\)\(t\) 的最短路是否发生改变 .

考虑 \(s\)\(t\) 的最短路图,于是问题变成删边判连通性 .

这个可以 DAG 上支配树做到 \(O(n\log n)\) .

此处可以 tarjan 求割边做到线性 .

Code

1. 灾难 代码(DAG 的支配树)

using namespace std;
typedef long long ll;
const int N=100005,M=2*N,MOD=1e9+7;
int head[N],shead[N],ver[M],nxt[M],tot=-1; // 链式前向星相关
int n,m,deg[N]; // 输入及拓扑相关
int father[N],siz[N]; // 支配树相关,father[u] 就是 u 在支配树上的父亲
int dep[N],,f[N][33]; // 倍增 LCA 相关(这里因为倍增方便就用倍增力)
void addedge(int head[],int u,int v){ver[++tot]=v; nxt[tot]=head[u]; head[u]=tot;}
int lca(int p1,int p2) // 倍增 LCA
{
    if (dep[p1]<dep[p2]) swap(p1,p2);
    for (int i=18;i>=0;i--)
        if (dep[f[p1][i]]>=dep[p2]) p1=f[p1][i];
    if (p1==p2) return p2;
    for (int i=18;i>=0;i--)
        if (f[p1][i]!=f[p2][i]) p1=f[p1][i],p2=f[p2][i];
    return f[p1][0];
}
void topsort() // 拓扑排序
{
	queue<int> q;
	for (int i=1;i<=n;i++)
		if (!deg[i]) q.push(i),father[i]=0; // 在入队时顺便连上虚拟节点
	while (!q.empty())
	{
		int u=q.front(),fa=father[u]; q.pop();
		addedge(shead,fa,u);
		f[u][0]=fa; dep[u]=dep[fa]+1;
		for (int i=1;i<=22;i++) f[u][i]=f[f[u][i-1]][i-1]; // 倍增 LCA 预处理
		for (int i=head[u];~i;i=nxt[i])
		{
			int v=ver[i]; --deg[v];
			if (!deg[v]) q.push(v);
			if (father[v]==-1) father[v]=u; // 所有直接食物的 LCA 即为父亲
			else father[v]=lca(father[v],u);
		}
	}
}
void dfs(int x) // 统计答案
{
	siz[x]=1;
	for (int i=shead[x];~i;i=nxt[i]){dfs(ver[i]); siz[x]+=siz[ver[i]];}
}
int main()
{
	memset(head,-1,sizeof head); memset(shead,-1,sizeof shead);
	memset(father,-1,sizeof father);
	scanf("%d",&n);
	for (int i=1,x;i<=n;i++)
		while (~scanf("%d",&x)&&x) addedge(head,x,i),++deg[i];
	topsort(); dfs(0);
	for (int i=1;i<=n;i++) printf("%d\n",siz[i]-1); // -1 是为了去掉自己
	return 0;
}

2. 【模板】支配树


typedef long long ll;
const int N=20500,M=20*N;
int n,m;
int head[N],shead[N],ihead[N],rhead[N],ver[M],nxt[M],tot=-1; // 链式前向星相关 
int siz[N],father[N],fa[N]; // 并查集与支配树相关 
int dfn[N],id[N],low[N],sdom[N],idom[N],cnt; // 支配点相关 
void addedge(int head[],int u,int v){ver[++tot]=v; nxt[tot]=head[u]; head[u]=tot;}
void init(int n){for (int i=1;i<=n;i++) sdom[i]=low[i]=fa[i]=i;}
void dfs1(int u) // dfs 树相关 
{
	dfn[u]=++cnt; id[cnt]=u;
	for (int i=head[u];~i;i=nxt[i])
	{
		int v=ver[i];
		if (dfn[v]) continue;
		father[v]=u; dfs1(v);
	}
}
int get(int u) // 并查集 
{
	if (u==fa[u])return u;
	int root=get(fa[u]);
	if (dfn[sdom[low[fa[u]]]]<dfn[sdom[low[u]]]) low[u]=low[fa[u]];
	return fa[u]=root;
}
void tarjan()
{
	int v;
	for (int i=cnt;i>1;i--) // 从叶子节点开始求 sdom
	{
		int u=id[i];
		for (int j=rhead[u];~j;j=nxt[j])
		{
			v=ver[j];
			if (!dfn[v]) continue;
			get(v);
			if (dfn[sdom[low[v]]]<dfn[sdom[u]]) sdom[u]=sdom[low[v]];
		}
		addedge(shead,sdom[u],u);
		fa[u]=father[u]; //合并
		u=father[u];
		for (int j=shead[u];~j;j=nxt[j]) // 求 idom 
		{
			v=ver[j]; get(v);
			if (sdom[low[v]]==u) idom[v]=u;
			else idom[v]=low[v];
		}
		shead[u]=-1; //清空父节点的边
	}
	for (int i=2;i<=cnt;i++)
	{
		int u=id[i];
		if (idom[u]!=sdom[u]) idom[u]=idom[idom[u]];  // 用引理调整 idom
	}
}
void dfs2(int u) // 统计 
{
	siz[u]=1;
	for(int i=ihead[u];~i;i=nxt[i])
	{
		int v=ver[i];
		dfs2(v);
		siz[u]+=siz[v];
	}
}
int main()
{
	memset(head,-1,sizeof head);
	memset(ihead,-1,sizeof ihead);
	memset(rhead,-1,sizeof rhead);
	memset(shead,-1,sizeof shead);
	scanf("%d%d",&n,&m);
	for (int i=1,u,v;i<=m;i++){scanf("%d%d",&u,&v); addedge(head,u,v); addedge(rhead,v,u);}
	init(n); // !!!!!!
	dfs1(1);
	tarjan();
	for (int i=2;i<=n;i++)
		if (idom[i]) addedge(ihead,idom[i],i);
	dfs2(1);
	for (int i=1;i<=n;i++) printf("%d ",siz[i]);
	return 0;
}
posted @ 2021-07-31 08:07  Jijidawang  阅读(135)  评论(0编辑  收藏  举报
😅​