割点和强连通分量

written by oneman233
2019-08-18

tarjan求割点(无向图)

定理:

割边一定依附于割点,而有割点不一定有割边

同时给出定义:

在一次dfs当中,由u访问到v:
称u为v的父亲节点,v为u的孩子节点,
u之前被访问到的是v的祖先节点

割点的判定方法:

若u的全部孩子可以不通过u来访问祖先节点,那么u不是割点;
而如果至少存在一个这样的孩子,u就是那个孩子与祖先节点的割点

具体实现:

/*
    dep[i] 遍历顺序的时间戳,子节点的时间戳一定大于父节点,访问一次节点后其时间戳就被确定了
    low[i] i节点不通过父节点就能访问到的最小时间戳,初始low[i]=dep[i]
    从任意顶点出发都可以完成tarjan
*/
void tarjan(int u,int f)
{
	dfn[u]=low[u]=++timer;
	int son=0;
	for(auto v:e[u])
	{
		if(dfn[v]==0)
		{
			tarjan(v,u);
			low[u]=min(low[u],low[v]);
			if(low[v]>=dfn[u]&&u!=f) is[u]=1;
			if(u==f) son++;
		}
		low[u]=min(low[u],dfn[v]);
	}
	if(son>=2&&u==f) is[u]=1;//point on the top
}

在上面的函数中,可以使用下面的形式:

\[tarjan(i,i)\]

在某些不一定联通的图中可以有效处理根节点是割点的情况

tarjan求桥(无向图)

定理:

对于所有的u的子节点v:

\[\begin{cases} u是割点, &if \ low[v]>=dep[u]\\ u-v是桥, &if \ low[v]>dep[u]\\ \end{cases}\]

具体实现:

void tarjan(int u,int f)
{
	fa[u]=f;
	dfn[u]=low[u]=++timer;
	for(auto v:e[u])
	{
		if(!dfn[v])
		{
			tarjan(v,u);
			low[u]=min(low[u],low[v]);
			//if(dfn[u]<low[v]) is[u][v]=1;
			//u is v's father
		}
		else if(v!=f) low[u]=min(low[u],dfn[v]);
	}
}

tarjan求强连通分量(有向图)

具体实现:

/*
    scc表示某标号的强连通分量中的点
    co表示某个点属于哪个强连通分量
*/
void tarjan(int u)
{
	dfn[u]=low[u]=++timer;
	s.push(u);
	vis[u]=1;
	for(auto v:e[u])
	{
		if(!dfn[v])
		{
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(vis[v]) low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u])
	{
		++color;
		int t;
		do
		{
			t=s.top();
			s.pop();
			co[t]=color;
			vis[t]=0;
			scc[color].push_back(t);
		}
		while(u!=t);
	}
}

tarjan求割点的一个拓展(无向图)

为了记录割点所分开的各联通块分别的点数,可以在这样修改tarjan:

/*
    此时part[u]当中储存的就是u号割点分开的各个联通块的个数
    需要注意的是它只能用于连通图,并且使用tarjan(1,0)
    它不能处理根节点是割点的情况
*/
void tarjan(int u,int f)
{
	dfn[u]=low[u]=++timer;
	sz[u]++;//
	int son=0,tmp=0;
	for(auto v:e[u])
	{
		if(dfn[v]==0)
		{
			tarjan(v,u);
			sz[u]+=sz[v];//
			low[u]=min(low[u],low[v]);
			if(low[v]>=dfn[u]&&u!=f)
			{
				is[u]=1;
				tmp+=sz[v];//
				part[u].push_back(sz[v]);//
			}
			if(u==f) son++;
		}
		low[u]=min(low[u],dfn[v]);
	}
	if(son>=2&&u==f) is[u]=1;//point on the top
	if(is[u]&&n-tmp-1!=0)
		part[u].push_back(n-tmp-1);//
}

有注释的部分是相对于求割点增加的代码,注意这种情况很容易爆int。