POJ2186:强连通分量

POJ2186

题解:

  • 找出一个强连通分量,判断其它边是否全部直接或者间接指向它
  • 利用Kosaraju找强连通分量
  • 如果找到了强连通分量A,下一个强连通分量B,在原有向图中,一定是A指向B。
  • 所以最后一个强连通分量必定是没有指向任何强连通分量的,也就是有可能成为被崇拜的。
  • 所以我们在最后一个分量开始在反向图中跑dfs2,如果每一个点都被遍历到了,说明所有的点都指向了它。
  • 还有一种想法是判断出度的个数,如果只有一个连通分量的出度为0,那么满足题意。在Tarjan算法中有写到。

代码:Kosaraju

#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
int const N = 10000 + 10;
int n,m,scc,cnt;
int vis[N],sccno[N];
vector<int>G1[N],G2[N],s;
void dfs(int u){
	vis[u] = true;
	for(int i=0;i<G1[u].size();i++){
		int v = G1[u][i];
		if(vis[v])	continue;
		vis[v] = true;
		dfs(v);
	}	
	s.push_back(u);    //反向记录拓扑序列
} 
void dfs2(int u){
	vis[u] = true;
	sccno[u] = scc;    //连通分块的编号
	for(int i=0;i<G2[u].size();i++){
		int v = G2[u][i];
		if(vis[v])	continue;
		dfs2(v);
	}
}
int main(){
	while(~scanf("%d%d",&n,&m)){
		s.clear();
		for(int i=0;i<=n;i++)	G1[i].clear(),	G2[i].clear();
		for(int i=0;i<m;i++){
			int from,to;
			scanf("%d%d",&from,&to);
			G1[from].push_back(to);
			G2[to].push_back(from);
		}
		memset(vis,false,sizeof(vis));
		for(int i=1;i<=n;i++)
			if(!vis[i])		dfs(i);
		memset(vis,false,sizeof(vis));
		for(int i=n-1;i>=0;i--){
			if(vis[s[i]])	continue;
			scc++;	dfs2(s[i]);	
		}
		int u;
		for(int i=1;i<=n;i++){
			if(sccno[i] == scc){   //找最后一个联通分块
				cnt++;
				u = i;
			}
		}
		memset(vis,false,sizeof(vis));
		dfs2(u);
		for(int i=1;i<=n;i++)
			if(!vis[i]){
				cnt = 0;
				break;
			}
		cout<<cnt<<endl;
	}
	return 0;
}

Tarjan

#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <stack>
#include <vector>
using namespace std;
int const N = 10000 + 10;
int n,m,scc,cnt;
int vis[N],sccno[N],lowlink[N],pre[N],sum[N],out[N];
vector<int>G[N];
stack<int>st;
void dfs(int u){
	lowlink[u] = pre[u] = ++cnt;
	st.push(u);
	for(int i=0;i<G[u].size();i++){
		int v = G[u][i];
		if(!pre[v]){
			dfs(v);
			lowlink[u] = min(lowlink[u],lowlink[v]);
		}else if(!sccno[v]){
			lowlink[u] = min(lowlink[u],pre[v]);
		}
	}
	if(lowlink[u] == pre[u]){
		scc++;
		while(1){
			int x = st.top();	st.pop();
			sum[scc]++;
			sccno[x] = scc;
			if(x == u)	break;
		}
	}	
}
void Tarjan(){
	cnt = scc = 0;
	memset(sum,0,sizeof(sum));
	memset(pre,0,sizeof(pre));
	memset(sccno,0,sizeof(sccno));
	for(int i=1;i<=n;i++)
		if(!pre[i])	dfs(i);
}
void solve(){
	memset(out,0,sizeof(out));
	for(int i=1;i<=n;i++)
		for(int j=0;j<G[i].size();j++){
			int v = G[i][j];
			if(sccno[i] != sccno[v])	out[sccno[i]]++;
		}
	int ans = 0;
	for(int i=1;i<=scc;i++){
		if(!out[i]){ 
			if(ans > 0){    //找到两个
				printf("0\n");
				return;
			}
			ans = sum[i];
		}
	}
	printf("%d\n",ans);
}
int main(){
	while(~scanf("%d%d",&n,&m)){
		for(int i=1;i<=n;i++)	G[i].clear();
		for(int i=0;i<m;i++){
			int from,to;
			scanf("%d%d",&from,&to);
			G[from].push_back(to);
		}
		Tarjan();
		solve();
	}
	return 0;
}

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值