圖論找環


競賽中找環有許多種問題,判斷是否有環,找到環上的點,找到環上的邊等等。

而只需要找到環上相鄰的兩點,或者環上的一條邊就可以解決這三個問題。

有向圖中,可以用拓撲排序的方法,把將拓撲排序完后限制條件仍未被清零的點即在環上的點。

#include <bits/stdc++.h>
#define N 1000101
using namespace std;
int n, m, cnt, lin[N], deg[N];
struct edg {
	int to, nex;
}e[N];
inline void add(int f, int t)
{
	deg[t]++;
	e[++cnt].nex = lin[f];
	e[cnt].to = t;
	lin[f] = cnt;
}
void topu()
{
	queue <int> q;
	for (int i = 1; i <= n; i++)	
		if (!deg[i]) q.push(i);
	while (!q.empty())
	{
		int cur = q.front(); q.pop();
		for (int i = lin[now]; i; i = e[i].nex)
		{
			int to = e[i].to;
			deg[to]--;
			if (!deg[to]) q.push(to);	
		}
	}
}
int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		add(a, b); 
	}
	topu();	 
	for (int i = 1; i <= n; i++)
		if (deg[i])
			printf("%d ", i);
	return 0;
}			 

無向圖中,就不能用拓撲排序了,比較好的方法就是並查集。

並查集通過判斷每一條邊的兩個端點是否在一個一個集合內來找到在同一個環上的兩個邊,然后以這兩個點為起點和終點搜索,最終輸出所有在他們路徑上的點。

#include <bits/stdc++.h>
#define N 1001011
using namespace std;
int n, m, cnt, fa[N], lin[N], vis[N], ha[N];
int find(int a)
{	
	if (fa[a] == a)
	return a;
	return fa[a] = find(fa[a]);
}	
struct edg {
	int to, nex, from;
}e[1001001];
inline void add(int f, int t )
{
	e[++cnt].to = t;
	e[cnt].from = f;
	e[cnt].nex = lin[f];
	lin[f] = cnt;
}
int dfs(int now, int end)
{
	if (now == end) return 1;
	int flag = 0;
	for (int i = lin[now]; i; i = e[i].nex)
	{
		int to = e[i].to;
		if (vis[to]) continue;
		vis[to] = 1;
		flag = max(flag, dfs(to, end));
		if (flag) ha[now] = ha[to] = 1;
		vis[to] = 0;	
	}
	return flag;
}
int main()
{
	scanf("%d%d", &n, &m); 
	for (int i = 1; i <= n; i++)
		fa[i] = i;
	for (int i = 1, a, b; i <= n; i++)
	{
		scanf("%d%d", &a, &b);
		add(a, b);
		add(b, a);
		int faa = find(a), fab = find(b);
		memset(vis, 0, sizeof(vis));
		if (faa == fab) //說明他們已經在同一個集合里 
			dfs(a, b);
		fa[faa] = fab;
	}
	for (int i = 1; i <= n; i++)
		if (ha[i]) 
			printf("%d ", i);
	return 0;
} 

以上都是邊權只有正的情況。

當然圖論中也有判斷負環和正環的情況(判斷正環,負環一般不區別有向圖、無向圖)

負環:

如果一個點在求最短路松弛操作超過n次則說明至少有一個點更新了它兩次,這說明一定出現了負環。

只需要用一個最短路算法即可判斷是否出現負環,一般用\(spfa\),因為\(Dijsktra\)不能用於存在負權邊的圖中。

正環:

可以將判負環的程序加邊時邊權取相反數,然后跑負環代碼,也可以用求最長路的松弛操作(一定要將初始dis設為無窮小)(求最長路只能用spfa來松弛,dij松弛是錯誤的)如果原圖中還是有負邊權則還是不能用\(Dijsktra\)算法。

spfa據說可以用dfs/bfs但是dfs不穩定,dfs只需要通過是否擴展的節點在當前的棧中,就可以判斷是否重復更新了。

bfs_spfa負環代碼:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>
using namespace std;
struct edge
{
	int from, to, len, nex;
} e[100100];
int cnt, lin[100010], b[100100], vis[100010], n, m, dis[100100];
bool flag;
inline void add(int u, int v, int l)
{
	e[++cnt].from = u;
	e[cnt].to = v;
	e[cnt].nex = lin[u];
	e[cnt].len = l;
	lin[u] = cnt;
}
queue <int> q;
int main()
{
	int t;	
	scanf("%d", &t);
	while (t--)
	{
		cnt = 0;
		flag = 0;
		memset(lin, 0, sizeof(lin));
		memset(vis, 0, sizeof(vis));
		memset(e,0,sizeof(e));
		memset(b,0,sizeof(b));
		scanf("%d%d", &n, &m);
		for (int i = 1; i <= n; i++)
			dis[i] = 2147483647;
		for (int i = 1; i <= m; i++)
		{
			int a, b, c;
			scanf("%d%d%d", &a, &b, &c);
			if (c < 0) add(a, b, c);
			else add(a, b, c), add(b, a, c);
		}
		q.push(1);
		dis[1] = 0;
		vis[1] = 1;
		b[1] = 1;
		while(!q.empty())
		{
			int cur = q.front();
			q.pop();
			b[cur] = 0;

			if (vis[cur] >= n || vis[1] >= 2)//只要比n大則說明此出現了負環
			{
				flag = 1;
				break;
			}
			for (int i = lin[cur]; i; i = e[i].nex)
			{
				int to = e[i].to;
				if (dis[e[i].to] > dis[cur] + e[i].len)
				{
					dis[e[i].to] = dis[cur] + e[i].len;
					if (!b[to])
					{
						vis[to]++;
						q.push(to);
						b[to] = 1;
					}
				}

			}
		}
		while(!q.empty())q.pop();
		if (flag) printf("YE5\n");
		else printf("N0\n");
	}
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM