图联通性问题(Tarjan)整理

笑死,根本学不会。(烂尾力

Tarjan算法是用于处理图连通性相关的一类算法。

1、强连通分量、双连通分量、割点与桥的定义

见 OI wiki

2、Tarjan算法的基本思想与框架

本质是通过构建 dfs 生成树,然后处理非树边来求出连通性相关的这些量。

dfs 生成树的标记通过dfn序(dfs到某个点的顺序构建),处理非树边需要一个low值(通过望子树走的树边或者子树上的至多一条非树边能到达的dfn值最小的点)。

void tarjan(int x)
{
	low[x]=dfn[x]=++num;
	for(int i=H[x];i;i=K[i])
	{
		int y=V[i];
		if(!dfn[y])
			tarjan(y,i),low[x]=min(low[x],low[y]);
		else
			low[x]=min(low[x],dfn[y]);
	}
}

3、强连通分量

非树边有三种:回边,横叉边,前向边。

  • 前向边对答案无贡献,不作讨论。

  • 回边一定有贡献,且满足low值的条件,更新为low。

  • 横叉边只有指向能回到dfs树上祖先的点才有贡献。

综上,我们可以用一个栈记录一些节点,这些节点要么是当前点的祖先节点,要么能够到达祖先节点。

每dfs到时节点入栈。

不在栈中的横叉边指向节点不能更新low值,因为这些点回不到祖先,不能产生贡献。

当存在点 \(dfn(x)=low(x)\) 时说明以x为根的子树构成了一个强连通分量,无法从此到达祖先节点,所以x及其上方的节点退栈,并计为一个强连通分量。

void tarjan(int x)
{
	low[x]=dfn[x]=++num;
	st[++top]=x,ins[x]=1;
	for(int i=H[x];i;i=K[i])
	{
		int y=V[i];
		if(!dfn[y])
			tarjan(y),low[x]=min(low[x],low[y]);
		else if(ins[y])
			low[x]=min(low[x],dfn[y]);
	}
	if(dfn[x]==low[x])
	{
		++cnt;
		for(;st[top]!=x;)
		{
			bl[st[top]]=cnt,a2[cnt]+=a[st[top]];
			ins[st[top]]=0,--top;
		}
		bl[x]=cnt,a2[cnt]+=a[x];
		ins[x]=0,--top;
	}
}
//in main
for(int x=1;x<=n;x++)
		if(!dfn[x]) tarjan(x);

4、桥与边双连通分量

\(dfn(x)<low(y)\)

void tarjan(int x,int in_e)
{
	low[x]=dfn[x]=++num;
	for(int i=H[x];i;i=K[i])
	{
		int y=V[i];
		if(!dfn[y])
		{
			tarjan(y,i),low[x]=min(low[x],low[y]);
			if(low[y]>dfn[x])
			{
				int u=V[i],v=V[i^1];
				if(u>v) swap(u,v);
				bdg.push_back(mkp(u,v));
			}
		}
		else if(i!=(in_e^1))
			low[x]=min(low[x],dfn[y]);
	}
}
//in main
for(int x=1;x<=n;x++)
		if(!dfn[x]) tarjan(x,-1);

5、割点与点双连通分量

\(dfn(x) \leq low(y)\)

注意根节点需要两个满足条件的 \(y\)

void tarjan(int x,int rt)
{
	low[x]=dfn[x]=++num;
	int flag=0;
	for(int i=H[x];i;i=K[i])
	{
		int y=V[i];
		if(!dfn[y])
		{
			tarjan(y,rt),low[x]=min(low[x],low[y]);
			if(low[y]>=dfn[x])
			{
				++flag;
				if(x!=rt || flag>1) cut[x]=1;
			}
		}
		else
			low[x]=min(low[x],dfn[y]);
	}
}
posted @ 2021-11-19 21:38  Coinred  阅读(46)  评论(0编辑  收藏  举报