Popular Cows POJ - 2186 有向图的双连通分量

//对于一个有向图,连通分量:对于分量中任意两点uv,
//必然可以从u走到v,也可以从v走到u
//强连通分量(scc):极大连通分量,也就是加上任何一个点之后,都不是连通分量

//有向图通过缩点,转化为有向无环图(DAG),拓扑图
//缩点是指将所有连通分量缩成一个点

//Tarjan算法求scc
//对每个点定义两个时间戳
//dfn[u]表示遍历到u的时间戳
//low[u]表示从u开始走,所能遍历到的最小时间戳
//u是其所在强连通分量的最高点,等价于dfn[u]==low[u]

//时间复杂度O(n+m)

//缩点,先遍历所有点i
//再遍历i的所有邻点
//如果i和j不在同一个连通分量中,加一条新边,id[i]->id[j]
//那么就把图建出来了,有向无环图DAG
//按照拓扑序来做
//连通分量编号递减的顺序一定是拓扑序
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 10010, M = 50010;
int n, m;
int h[N], e[M], ne[M], idx;
int dfn[N], low[N], timestamp;
//          栈顶
int stk[N], top;
bool in_stk[N];
//属于哪个连通分量
int id[N];
//当前有多少强连通分量
int scc_cnt;
//每个强连通分量中点的数量
int Size[N];
//每个连通分量的出度
int dout[N];
void add(int a, int b)
{
	e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void tarjan(int u)
{
	//先都等于时间戳
	dfn[u] = low[u] = ++ timestamp;
	//把当前点加到栈当中去
	//栈当中存的值,不单单是当前路径上的值,还可能存其他边上的点
	//存的点,都不是它所在强连通分量的最高点
	//都是,当前,还没有搜完的遍历完的,强连通分量的所有点
	stk[ ++ top] = u;
	//记录是否在栈当中
	in_stk[u] = true;
	//遍历u的所有临点
	for (int i = h[u]; i != -1; i = ne[i])
	{
		int j = e[i];
		//如果还没有被遍历过
		if (!dfn[j])
		{
			//遍历
			tarjan(j);
			//更新
			low[u] = min(low[u], low[j]);
		}
		//否则,说明遍历过,而且还在栈当中
		else if (in_stk[j])
			//取最小
			low[u] = min(low[u], dfn[j]);
	}
	//遍历完u之后,发现u能到的最前面的点就是自己了
	if (dfn[u] == low[u])
	{
		//那就说明,u肯定是所在强连通分量的最高点
		//所有强连通分量个数++
		++ scc_cnt;
		int y;
		do
		{
			//先取出栈顶元素
			y = stk[top -- ];
			//表示出栈
			in_stk[y] = false;
			//标记当前点属于哪个强连通分量
			id[y] = scc_cnt;
			//
			Size[scc_cnt] ++ ;
			//y==u时,表示所在强连通分量处理完了
		}
		while (y != u);
	}
}
int main()
{
	scanf("%d%d", &n, &m);
	memset(h, -1, sizeof h);
	while (m -- )
	{
		int a, b;
		scanf("%d%d", &a, &b);
		add(a, b);
	}
	//建新图
	for (int i = 1; i <= n; i ++ )
		if (!dfn[i])
			tarjan(i);
	//统计所有点的出度
	//遍历原图的所有边
	for (int i = 1; i <= n; i ++ )
		for (int j = h[i]; ~j; j = ne[j])
		{
			int k = e[j];
			int a = id[i], b = id[k];
			//从a走到b
			//所有a的出度增加
			if (a != b)
				dout[a] ++ ;
		}
	int zeros = 0, sum = 0;
	for (int i = 1; i <= scc_cnt; i ++ )
		if (!dout[i])
		{
			//统计有多少个点的出度为0
			//如果只有一个,那么最终答案就是出度为0的点的size
			//如果大于一个,那么出度为0的强连通块必然不能互相到达
			//所有就是0
			zeros ++ ;
			sum += Size[i];
			if (zeros > 1)
			{
				sum = 0;
				break;
			}
		}
	printf("%d\n", sum);
	return 0;
}

posted @ 2020-05-08 17:39  晴屿  阅读(174)  评论(0编辑  收藏  举报