一般图的支配树

P5180【模板】支配树

来说说一般图上的支配树。

(前排提醒:本文实质是对老师讲的内容的补充,阅读本文前应该知道 \(\operatorname{idom}\) 及其在 DAG 上的求解方法,具体可以去查看 ZJOI2012 灾难 的题解。)

常见的做法是 Languaer-Tarjan 算法,该算法的核心在于提出了半支配点 \(\text{sdom}\)

半支配点 \(\operatorname{sdom}\) 的定义是除起点和终点外只经过 DFS 序大于终点的点的起点中 DFS 序最小的一个。

形式化说,将点按照 DFS 序编号后,\(\operatorname{sdom }x=\min\{v\mid\exist v=v_0\rightarrow v_1\rightarrow\cdots\rightarrow v_k=x,\forall 1\le i\lt k,v_i\gt x\}\)

这里在本文中额外定义 半支配 关系,即满足上述条件,但不一定是最小值的那些 \(v\) 都是 半支配 \(x\) 的,当然 \(\operatorname{sdom }x\)半支配 \(x\)

这东西看上去屁点性质没有,先不说怎么拿它求答案,根本就求不出来吧?

先暴力考虑怎么求解 \(\operatorname{sdom}\) 吧,下面点的编号即为其 DFS 序。

对于一个点 \(u\) 进行求解,则考虑其前驱 \(v\),分类讨论答案的候选值:

  • \(v\lt u\),此时 \(v\) 就直接小于 \(u\) 了,显然只能拿 \(v\) 作为起点,所以 \(v\) 是当前情况唯一可能的候选值,另外其实这种情况只有可能在 \(v\)\(u\) 在 DFS 树上的父亲时成立。
  • \(v\gt u\),这个时候 \(v\) 可以作为路径中间的点,于是 \(\operatorname{sdom }v\) 是一个候选值,而此时 \(v\) 可以继续往 DFS 树的父亲走,如果其父亲 \(w\gt u\),这个过程就还能够继续,直到发现 \(w\lt u\) 的时候变成第一种情况为止。

容易发现上面的部分没有漏掉任何一个可能的候选值,这时候做一个暴力从所有候选值中取最小值即可。

发现时间复杂度的瓶颈位于第二种情况,有什么办法优化一下?

考虑按照 DFS 序的逆序 处理。

此时已经处理的点天然满足大于的条件,因此我们可以把第二种情况查询的信息简化为 \(v\) 上面所有已经完成更新的祖先的 \(\operatorname{sdom}\) 的最小值。

注意到维护的信息非常简单,我们可以使用带权并查集简单维护,注意这里不能按秩合并,因为我们的并查集维护的是处理到目前的 DFS 树,形态不能够变。

此时我们能够处理出所有点的 \(\operatorname{sdom}\) 了,时间复杂度为 \(\mathcal{O}(n\log n)\)

不过就算知道了 \(\operatorname{sdom}\) 又有啥用啊真的是……

我们最终要求的是 \(\operatorname{idom}\) 啊,这个 \(\operatorname{sdom}\) 和它有啥关系嘛……

分析一下,关于 \(\operatorname{idom }x\) 有一个性质,即 \(\operatorname{idom }x\) 一定在 DFS 树上根节点到 \(x\) 的路径上,考虑 \(\operatorname{idom}\) 的定义就可以得到。

\(\operatorname{sdom}\) 很显然告诉我们,\(\operatorname{sdom }x\) 子树内的点(不包括它)都不可能成为 \(\operatorname{idom }x\),因为这里存在一条其它的路径不经过它们。

这下 \(\operatorname{sdom}\) 看上去有用了,分析一下怎样求解?

(只说 DAG 做法,另一个我太烂了不会。)

我们往已有的做法上靠,就是 DAG 上的做法,能不能把原图换成一个支配关系相同的 DAG?

要这么做肯定不能虚空建图,我们考虑保留一点原图的元素。

分析一下,我们可以留下 DFS 树,这样子原有的支配关系全部都在,只是多出了一些本来没有的支配关系。

考虑去掉多余的支配关系,设原本不满足支配关系的点对 \(u,v\),其中 \(u\) 不支配 \(v\)

现在这个点对满足支配关系,我们可以推出 \(u\)\(v\) 的祖先(\(u\)\(v\) 的情况就不讨论了)。

如果 \(u\) 原本不支配 \(v\),则存在一条路径不经过 \(u\) 到达 \(v\),我们分析一下这条路径什么情况。

挂一张图:

我们可以考虑根节点到 \(v\) 的这条路径,上面必然经过点 \(u\)

而显然如果要不经过 \(u\)\(v\),必然有两个点 \(x,y\) 满足 \(x\) 能够到达 \(y\)\(x\)\(u\) 的祖先,\(u\)\(y\) 的祖先。

不难发现 \(y\) 一定是通过某条横叉边到达的,由于这是有向图的 DFS 树,所以横叉边一定是由 DFS 序大的点连向小的点的。

容易推出 \(x\)\(y\) 的路径上所有点的 DFS 序均大于 \(y\),所以 \(x\) 半支配 \(y\)

显然我们连接 \(x,y\) 即可去掉 \(u,v\) 中多余的支配关系,而连接 \(\operatorname{sdom }y,y\) 显然也能够达成一致的效果,因为 \(\operatorname{sdom }x\) 是所有 半支配 \(x\) 的点中深度最浅的那个。

此时没有多余的支配关系了,又因为 \(\operatorname{idom }x\)\(\operatorname{sdom }x\) 的祖先,所以支配关系不会因为这样的操作变少。

不难发现新图是 DAG,于是我们完成了一般图的支配树的求解。

挂个代码吧,没什么参考作用。

#include<bits/stdc++.h>
using namespace std;
constexpr int N=200010;
vector<int>G[2][N],e[N],T[N];
int dfn[N],rev[N],tot,fa[N];
void dfs1(int u)
{
	tot++;
	dfn[u]=tot;
	rev[tot]=u;
	for(int v:G[0][u])if(!dfn[v])dfs1(v),fa[v]=u,e[v].push_back(u);
	return;
}
int dsu[N],val[N],sdom[N];
pair<int,int>F(int x)
{
	if(dsu[x]==x)return make_pair(x,sdom[x]);
	pair<int,int>tmp=F(dsu[x]);
	val[x]=min(val[x],tmp.second);
	dsu[x]=tmp.first;
	return make_pair(dsu[x],val[x]);
}
constexpr int MX=15;
int f[N][MX],d[N];
inline int LCA(int u,int v)
{
	if(d[u]<d[v])swap(u,v);
	for(int i=MX-1;i>=0;i--)if(d[f[u][i]]>=d[v])u=f[u][i];
	if(u==v)return u;
	for(int i=MX-1;i>=0;i--)if(f[u][i]!=f[v][i])u=f[u][i],v=f[v][i];
	return f[v][0];
}
int sz[N];
void dfs2(int u)
{
	sz[u]=1;
	for(int v:T[u])if(!sz[v])dfs2(v),sz[u]+=sz[v];
	return;
}
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int a,b;
		scanf("%d%d",&a,&b);
		G[0][a].push_back(b);
		G[1][b].push_back(a);
	}
	dfs1(1);
	iota(dsu+1,dsu+n+1,1);
	memset(val,0x7f,sizeof val);
	swap(n,tot);
	for(int i=n;i>1;i--)
	{
		int u=rev[i];
		sdom[u]=n+1;
		for(int v:G[1][u])
		{
			if(dfn[v]<i)sdom[u]=min(sdom[u],dfn[v]);
			else sdom[u]=min(sdom[u],F(v).second);
		}
		for(int v:G[0][u])if(fa[v]==u)dsu[v]=u,val[v]=sdom[v];
	}
	for(int i=2;i<=n;i++)sdom[i]=rev[sdom[i]];
	for(int i=2;i<=n;i++)e[i].push_back(sdom[i]);
	d[1]=1;
	for(int i=2;i<=n;i++)
	{
		int u=rev[i];
		int idom=-1;
		for(int v:e[u])
		{
			if(idom==-1)idom=v;
			else idom=LCA(idom,v);
		}
		f[u][0]=idom;
		d[u]=d[idom]+1;
		for(int i=1;i<MX;i++)f[u][i]=f[f[u][i-1]][i-1];
		T[idom].push_back(u);
	}
	dfs2(1);
	printf("%d",n);
	for(int i=2;i<=tot;i++)printf(" %d",sz[i]==0?1:sz[i]);
	return 0;
}
posted @ 2024-05-23 17:34  小山云盘  阅读(33)  评论(0)    收藏  举报