强连通分量算法学习笔记

强连通分量


更好阅读体验请点击此链接

先推荐几道综合性较强的题目,详细题解我写了,持续更新中。

我的博客:整理 + 题解

P2341 [USACO03FALL][HAOI2006]受欢迎的牛 G

P2272 [ZJOI2007]最大半连通子图


强连通分量( strongly connected components ),可以理解为存在一条回路使得初始点又能回到初始点。

有两种算法, 这里分享的是 Tarjan (我用前向星存图)。


1. 基本概念:

Tarjan 基于对图深搜的算法,每个强连通分量为搜索树中的一棵子树。 搜索时把没处理的结点加入一个栈,回溯时判断当前结点能否与栈中结点构成一个强连通分量。 (我用数组模拟的栈。)

而我们要做的就是分别找到一条条回路,使得初始点又回到初始点(对于该有向子图,任意两点 G u , v G_u,_v Gu,v 有一条从 u 到 v 的路径,亦有一条从 v 到 u 的路径)。

后面附了图,可以结合示例理解。

2. 数组、栈:

板子一般开两个数组以及一个栈, dfn 数组, low 数组, st 栈。

定义如下:
  • dfn 数组:定义 d f n u dfn_u dfnu 为结点 u 的搜索次序编号(时间戳);

  • low 数组:定义 l o w u low_u lowu 表示结点 u 或者 u 的子树最多能够回溯(到达)到的结点 x , 使得 d f n u dfn_u dfnu 最小;

  • st 栈:每找到一个点就把它压入栈中,这是为了之后找一个强连通分量里所有的子节点。

用法、作用如下:
  • dfn 数组:记录时间戳(编号);

  • low 数组:当一个点最多能回溯(到达)到的点还是自己时,说明这条路径已经成环,根据前面说过的两条定义,这个点以及路径上的节点形成了一个强连通分量。

    同时,我们在找强连通分量的过程中,也要不断地用它能到达的节点的 dfn 值去松弛它自己的 dfn 值;

  • st 栈:当我们从点 u 走完了所有可以到达的点后 ,满足 l o w u = d f n u low_u=dfn_u lowu=dfnu 时,我们将栈中的点逐一弹出,解释如下:

    我们在第一次找到点 u 时,已经将它入栈了。且我们在经过了若干节点并将它们按先后顺序并分别入栈后,发现从 u 和若干节点成一个强连通分量了。

    这时候我们想找到这个强连通分量内所有的节点,我们要怎么做?

    逐一从栈中弹出。从栈顶到栈中的 u ,都是我们要的当前强连通分量内的节点(从 u 开始我们都把它们压入栈中了),所以,我们只需要一直把栈顶从栈中弹出,直到弹出点 u 。

3. 算法主要过程:

概述: 通过深度优先搜索和入栈出栈的操作来找到一条回路,使回路初始点又能回到初始点。

  1. 记录 d f n u dfn_u dfnu 以及 l o w u low_u lowu

  2. 将 u 入栈;

  3. 前向星( vector 当然也可以)枚举每一条边,更新 l o w u low_u lowu

  • 如果结点 v (枚举到的点)没有被访问过:访问v,如果 l o w v low_v lowv 小于 l o w u low_u lowu ,就用前者更新后者;

  • 如果结点 v 仍在栈内即 v 还不属于仍何强连通分量,并用 d f n v dfn_v dfnv 来刷新 l o w u low_u lowu (在前者小于后者的情况下)。

  1. 如果形成了一条回路,即边弹出栈边统计当前节点的强连通分量编号即可,直到弹出了 u ,此时被弹出的所有节点,就形成了一个强连通分量。
4. 画图示例:

示例

如图所示,在经过如上所说的操作后,一幅图可以被分成若干强连通分量。

示例中就被分成了 3 个强连通分量,可结合强连通分量的定义理解。


以上就是 Tarjan 算法,还是不明白可以见代码或私信问我。

此题其他的操作并不难,这里也不多阐述了。


完整代码:

#include<bits/stdc++.h>
using namespace std;

#define int long long
const int maxn = 100005;
int n, m;
int cnt, hd[maxn];
struct node{
	int to, nxt;
}e[maxn * 2]; 
int dfn[maxn], low[maxn];
int tmp, top;
int st[maxn];
int co[maxn], col;

void add (int u, int v)
{
	e[++cnt].to = v;
	e[cnt].nxt = hd[u];
	hd[u] = cnt;
}

void tarjan (int u)
{
	dfn[u] = low[u] = ++tmp;//step 1
	st[++top] = u;//step 2
	for (int i = hd[u]; i; i = e[i].nxt)//step 3
	{
		int v = e[i].to;
		if (!dfn[v])//如果没有被访问过
		{
			tarjan (v);
			low[u] = min (low[u], low[v]);
		}
		else if (!co[v]) //如果它还不在一个强连通分量内
        low[u] = min (low[u], dfn[v]);
		
	}
	if (low[u] == dfn[u])//step 4
	{
		co[u] = ++col;
		while (st[top] != u)//弹栈操作
		{
			co[st[top]] = col;
			--top;
		} 
		--top;//最后记得把 u 也弹出去
	}
}

int vis[maxn];

signed main ()
{
	scanf ("%lld %lld", &n, &m);
	for	(int i = 1; i <= m; i++)
	{
		int u, v;
		scanf ("%lld %lld", &u, &v);
		add (u, v);
	}
	for (int i = 1; i <= n; i++)
	{
		if (!dfn[i]) tarjan (i);
	}
	printf ("%lld\n", col);
	for (int i = 1; i <= n; i++)
	{
		if (vis[i]) continue;
		printf ("%lld ", i);
		vis[i] = 1;
		for (int j = i + 1; j <= n; j++)
		{
			if (co[j] == co[i])
			{
				printf ("%lld ", j);
				vis[j] = 1;
			}
		}
		printf ("\n");
	}
	return 0;
}

—— E n d \mathfrak{End} End——

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值