強連通分量及縮點 演算法解析及例題

usernamezzz發表於2019-02-15

演算法講解: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;
}


 

相關文章