洛谷P2341 强连通分量

题面

每一个奶牛都喜欢自己,除此以外还有一些喜欢的对象,用边表示。
并且奶牛的喜欢符合传递关系,即A喜欢B,B喜欢C则A喜欢C
被全体奶牛喜欢的奶牛称为明星
给出N头奶牛和M个喜欢关系,求明星数量

1<=N<=10000,1<=M<=50000

分析

如果整个图只是一个强连通分量,那么显然所有点间都可相互到达(喜欢都能传递到),所以图内所有点都是明星。

考虑有多个强连通分量的情况,可以把每个强连通分量先缩点。
F→C
像这种情况,因为有F→C的边,所以分量2中的点都能到达分量1,分量1的出度为0,也就可以做到让分量1内的ABC成为明星

而如果删去F→C的边,分量1和2的出度都为0,就不可能做到某个牛成为明星,因为另一个分量的牛都不喜欢它

经过思考,最终的情况其实就是:当存在大于等于2个分量出度为0时,就不存在明星(如两个出度0的分量中取一个牛,另一个分量中的牛都不喜欢它,不满足所有牛都喜欢)
1个分量出度为0时,其余分量的喜欢可以传递给它,所以这个分量内所有牛都是明星
0个分量出度0,空图。。。就不存在了。

这是求完强连通分量之后的缩点问题,关于求强连通分量,我用的是tarjan的板子。
tarjan法求强连通分量的算法其实和求割点差不多,都用到了pre,low函数,一样的dfs。

标记分量的方法就是找到dfs中第一个进入某个分量的元素,这个元素的特点是low[u]=pre[u],在dfs过程中用一个栈存下还没有分配连通块的元素,一旦遇到了low[u]=pre[u],就连续出栈到u,这一次出栈的元素都属于同一个强连通分量。

代码

#include<iostream>
#include<vector>
#include<stack>
#include<algorithm>
using namespace std;
vector<int> G[50004];
int dfs_clock = 0, scc_cnt = 0;//时间戳,强连通分量组编号
int sccno[10004];//表示每个点属于哪个强连通块
int pre[10004];//dfs序
int low[10004];//low[u]表示u及其后带通过反向边最多能返回到哪
stack<int> s;//存当前的节点
int out[10004];//出度统计,序号是强连通分量的编号
void dfs(int u)
{
	pre[u] = low[u] = ++dfs_clock;
	s.push(u);
	for (int i = 0; i < G[u].size(); i++)
	{
		int v = G[u][i];//下一个
		if (!pre[v])//未到过v
		{
			dfs(v);
			low[u] = min(low[u], low[v]);
		}
		else if (!sccno[v])//有可能是新的反向边
		{
			low[u] = min(low[u], pre[v]);
		}
	}
	if (low[u] == pre[u])//是第一次发现这个连通分量的点
	{
		scc_cnt++;
		while (true)
		{
			int x = s.top();s.pop();
			sccno[x] = scc_cnt;
			if (x == u)break;
		}
	}
}
void find_scc(int n)
{
	for (int i = 1; i <= n; i++)
	{
		if (!pre[i])dfs(i);
	}
}
int main()
{
	ios::sync_with_stdio(false);
    int n, m,u,v;
    cin >> n >> m;
	for (int i = 0; i < m; i++)
	{
		cin >> u >> v;
		G[u].push_back(v);
	}
	find_scc(n);//开始寻找强连通分量的函数

	//sccno已经处理完,1~scc_cnt
	//开始处理每个块的出度问题
	for (int i = 1; i <= n; i++)
	{
		for (int j = 0;j < G[i].size();j++)//(i,G[i][j])是一条边
		{
			if (sccno[i] != sccno[G[i][j]])out[sccno[i]]++;//不是同一个块,则i的出度++
		}
	}

	int ans = 0;//此时记录out为0的块的个数
	for (int i = 1; i <= scc_cnt; i++)
	{
		if (out[i] == 0)ans++;
	}
	if (ans > 1)cout << 0;//有2个及以上出度0的块,不存在明星
	else {
		ans = 0;
		for (int i = 1; i <= n; i++)
		{
			if (out[sccno[i]] == 0)ans++;//输出出度0的块内所有点
		}
		cout << ans;
	}
    return 0;
}


求强连通分量部分参考了《算法竞赛入门经典-训练指南》的板子

发布了32 篇原创文章 · 获赞 0 · 访问量 1185

猜你喜欢

转载自blog.csdn.net/engineoid/article/details/104217111