强连通分量及缩点 算法解析及例题

算法讲解:https://blog.csdn.net/acmmmm/article/details/16361033#comments

例题:

poj-2186

http://poj.org/problem?id=2186

题意:总共有n头牛,m个有序对(a, b) 代表牛a崇拜牛b, 崇拜具有传递性,求出被其他所有牛崇拜的牛的总数。

题解:构造一个图,进行求强连通分量及缩点,在强连通分量中的奶牛肯定是被在这个强连通分量里面奶牛所仰慕的,如果一个图里面有几个强连通分量的话那么一定至少存在一个出度为0的点,如果不存在那么刚刚的强连通一定不是最大强连通又想如果有两个这样的出度为0的点存在,那么这个图中一定没有满足题意的奶牛,因为那两个出度为0的点不会仰慕任何牛所以出度为0的点有且只能有一个!如果有多个那么满足题意的牛只有0个,如果没有的话那么整个图都是满足题意的牛。

ac code:

#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
#include<queue>
#include<cstring>
#include<stack>
#include<vector>
using namespace std;
#define N 10005
struct eg {
	int to, next;
}ac[5*N];
int n, m, cnt, jar;
int head[N], dfn[N], low[N], instack[N], belong[N];
int nu[N];
vector<int>re[N];
stack<int>s;
void init() {
	memset(nu, 0, sizeof(nu));
	memset(head, -1, sizeof(head));
	memset(dfn, 0, sizeof(dfn));
	memset(instack, 0, sizeof(instack));
	cnt = 0; jar = 0;
}
void add(int u, int v) {
	ac[cnt].to = v;
	ac[cnt].next = head[u];
	head[u] = cnt++;
}
void tarjan(int u) {
	dfn[u] = low[u] = ++cnt;
	s.push(u);
	instack[u] = 1;
	for (int i = head[u]; i != -1; i = ac[i].next) {
		int v = ac[i].to;
		if (!dfn[v]) {
			tarjan(v);
			low[u] = min(low[u], low[v]);
		}
		else if (instack[v])low[u] = min(low[u], dfn[v]);
	}
	if (low[u] == dfn[u]) {
		int now;
		re[jar].clear();
		do {
			now = s.top();
			s.pop();
			instack[now] = 0;
			belong[now] = jar;
			re[jar].push_back(now);
		} while (now != u);
		jar++;
	}
}
int main() {
	int result = 0;
	int a, b;
	scanf("%d%d", &n, &m);
	init();
	for (int i = 0; i < m; i++) {
		scanf("%d%d", &a, &b);
		add(a, b);
	}
	cnt = 0;
	for (int i = 1; i <= n; i++) {
		if (dfn[i] == 0)
			tarjan(i);
	}
	for (int i = 1; i <= n; i++) {
		for (int j = head[i]; j != -1; j = ac[j].next) {
			int v = ac[j].to;
			if (belong[i] != belong[v]) {
				nu[belong[i]] = 1;
			}
		}
	}
	int ans = 0, t;
	for (int i = 0; i < jar; i++) {
		if (!nu[i]) {
			ans++;
			t = i;
		}
	}
	if (ans > 1) {
		cout << result << endl;
		return 0;
	}
	cout << re[t].size() << endl;
	return 0;
}

poj-2553

http://poj.org/problem?id=2553

求从u出发能到v,并且从v出发也能到u的这些点,并且将其输出;求出一个图的若干个强连通分量并且如果这个强连通分量中的缩点出度为0的话那么这个强连通分量里面的点就都满足题意。

ac code:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<stack>
#include<vector>
using namespace std;
#define N 5005
int n, e, cnt, ans;
int head[N], dfn[N], low[N], belong[N], nu[N], instack[N], vis[N];
vector<int>re[N];
struct eg {
	int to, next;
}a[10*N];
stack<int>s;
void init() {
	ans = cnt = 0;
	memset(head, -1, sizeof(head));
	memset(nu, 0, sizeof(nu));
	memset(dfn, 0, sizeof(dfn));
	memset(instack, 0, sizeof(instack));
	memset(vis, 0, sizeof(vis));
}
void add(int u, int v) {
	a[cnt].to = v;
	a[cnt].next = head[u];
	head[u] = cnt++;
}
void tarjan(int u) {
	dfn[u] = low[u] = ++cnt;
	s.push(u);
	instack[u] = 1;
	for (int i = head[u]; i != -1; i = a[i].next) {
		int v = a[i].to;
		if (!dfn[v]) {
			tarjan(v);
			low[u] = min(low[u], low[v]);
		}
		else if (instack[v])low[u] = min(low[u], dfn[v]);
	}
	if (low[u] == dfn[u]) {
		int now;
		re[ans].clear();
		do {
			now = s.top();
			s.pop();
			belong[now] = ans;
			re[ans].push_back(now); 
			instack[now] = 0;
		} while (now != u);
		ans++;
	}
}
int main() {
	int x, y;
	while (~scanf("%d", &n) && n != 0) {
		scanf("%d", &e);
		init();
		for (int i = 0; i < e; i++) {
			scanf("%d%d", &x, &y);
			add(x, y);
		}
		cnt = 0;
		for (int i = 1; i <= n; i++) {
			if (!dfn[i]) {
				tarjan(i);
			}
		}
		for (int i = 1; i <= n; i++) {
			for (int j = head[i]; j != -1; j = a[j].next) {
				int v = a[j].to;
				if (belong[v] != belong[i]) {
					nu[belong[i]] = 1;
				}
			}
		}
		for (int i = 0; i < ans; i++) {
			if (!nu[i]) {
				for (int j = 0; j < re[i].size(); j++) {
					vis[re[i][j]] = 1;
				}
			}
		}
		int res;
		for (int i = 1; i <= n; i++) {
			if (vis[i]) {
				res = i;
				cout << i;
				break;
			}
		}
		for (int i = res + 1; i <= n; i++)
			if (vis[i])
				cout << " " << i;
		cout << endl;
	}
	return 0;
}

poj-2762

http://poj.org/problem?id=2762

题意:一个有向图中有n个点m条边,问是否任意两点u,v之间可以u-v或者v->u。
思路:学过强连通可以知道,在一个强连通中任意两点都可以满足题目条件。如果图中有多个强连通分量,则需要在强连通分量之间建图,再拓扑排序判断图中是否同时存在一个入度为零的两点,如果存在那么这两点肯定不满足题目条件。

ac code:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<stack>
#include<set>
#include<vector>
using namespace std;
#define N 1005
int n, m, cnt, ans, t;
int head[N], dfn[N], low[N], belong[N], nu[N], instack[N], vis[N];
vector<int>re[N];
struct eg {
	int to, next;
}a[10 * N];
stack<int>s;
void init() {
	memset(head, -1, sizeof(head));
	memset(dfn, 0, sizeof(dfn));
	memset(instack, 0, sizeof(instack));
	memset(nu, 0, sizeof(nu));
	cnt = ans = 0;
}
void add(int u, int v) {
	a[cnt].to = v;
	a[cnt].next = head[u];
	head[u] = cnt++;
}
void tarjan(int u) {
	dfn[u] = low[u] = ++cnt;
	s.push(u);
	instack[u] = 1;
	for (int i = head[u]; i != -1; i = a[i].next) {
		int v = a[i].to;
		if (!dfn[v]) {
			tarjan(v);
			low[u] = min(low[u], low[v]);
		}
		else if (instack[v])low[u] = min(low[u], dfn[v]);
	}
	if (dfn[u] == low[u]) {
		int now;
		re[ans].clear();
		do {
			now = s.top();
			s.pop();
			re[ans].push_back(now);
			instack[now] = 0;
			belong[now] = ans;
		} while (now != u);
		ans++;
	}
}
void solve() {
	set<int>st[N];
	vector<int>h;
	int flag = 0;
	for (int i = 1; i <= n; i++) {
		for (int j = head[i]; j != -1; j = a[j].next) {
			int v = a[j].to;
			if (belong[v] != belong[i]) {
				st[belong[i]].insert(belong[v]);
			}
		}
	}
	for (int i = 0; i < ans; i++) {
		for (set<int>::iterator ite = st[i].begin(); ite != st[i].end(); ite++) {
			//cout << i << " " << *ite << endl;
			nu[*ite]++;
		}
	}                                          
	for (int i = 0; i < ans; i++) {
		if (!nu[i]) {
			h.push_back(i);
		}
	}
	while (1) {
		if (h.size() == 0)
			break;
		if (h.size() > 1) {
			flag = 1;
			cout << "No" << endl;
			break;
		}
		int t = h[0];
		h.clear();
		for (set<int>::iterator ite = st[t].begin(); ite != st[t].end(); ite++) {
			nu[*ite]--;
			//cout << *ite << " " << nu[*ite] << endl;
			if (nu[*ite] == 0)
				h.push_back(*ite);
		}
	}
	if (!flag)
		cout << "Yes" << endl;
}
int main() {
	int u, v;
	cin >> t;
	while (t--) {
		scanf("%d%d", &n, &m);
		init();
		for (int i = 0; i < m; i++) {
			scanf("%d%d", &u, &v);
			add(u, v);
		}
		for (int i = 1; i <= n; i++) {
			if (!dfn[i]) {
				cnt = 0;
				tarjan(i);
			}
		}
		if (ans == 1) {
			cout << "Yes" << endl;
		}
		else {
			solve();
		}
	}
	return 0;
}


 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值