首页 > 代码库 > HDU 3367 Pseudoforest(伪森林)(并查集)

HDU 3367 Pseudoforest(伪森林)(并查集)

题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=3367

题意:在图论中,如果一个森林中有很多连通分量,并且每个连通分量中至多有一个环,那么这个森林就称为伪森林。

   现在给出一个森林,求森林包含的最大的伪森林,其大小通过所有边的权值之和来比较。

分析:

1、一开始想的是:在每个连通分量中求一个最大生成树,然后加一条最大的边,再把每个连通分量算出来的值加起来,但WA了。这并不是最优的,因为还存在这种情况:一个连通分量里最初有两个环,但是伪森林要求最多一个环,所以你把其中一个环去掉,这样不能保证最优,因为有可能你把两个环都留下,然后删除一条较小的边把这个连通分量拆成两个连通分量的情况是最优的。

 2、最优的解法是:从大到小遍历每条边,能要的边就要,也就是说,除非加上这条边会导致形成两个环,否则就要这条边。用并查集来判断是否两个点相连(在同一个连通分量里),用一个标记数组来记录每个点是否在环所在的连通分量。

代码如下:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxm = 100000+10;
const int maxn = 10000+10;
struct Edge{
	int a, b, v;
	bool operator < (const Edge& t)const{
		return v > t.v;
	}
}edges[maxm];
bool vis[maxn];
int f[maxn];
int find(int x){
	return x == f[x] ? x : f[x] = find(f[x]);
}
int main(){
	int n, m;
	while (~scanf("%d%d", &n, &m)){
		if (n==0 && m==0) break;
		memset(vis, false, sizeof(vis));
		for (int i=0; i<n; i++) f[i] = i;
		for (int i=0; i<m; i++){
			scanf("%d%d%d", &edges[i].a, &edges[i].b, &edges[i].v);
		}
		sort(edges, edges+m);
		int ans = 0;
		for (int i=0; i<m; i++){
			int a = edges[i].a;
			int b = edges[i].b;
			a = find(a);
			b = find(b);
			if (a != b){//两个点在不同的连通分量里的情况
				if (!vis[a] && !vis[b]){//两个点都不在环内,直接加上这条边
					ans += edges[i].v;
					f[a] = b;
				}
				else if (!vis[a] || !vis[b]){//有一个点在环内,加上这条边并将这两个点标记为在环内
					ans += edges[i].v;
					f[a] = b;
					vis[a] = vis[b] = 1;
				}
				else{
					//都在环内,不能加这条边,跳过,可以省略这个语句 
				}
			}
			else{//两个点在同一个连通分量里的情况
				if (!vis[a] && !vis[b]){//如果两个点都不在环内,加上这条边,形成环,将两个点标记在环内 
					ans += edges[i].v;
					vis[a] = vis[b] = 1;
				}
			}
		}
		printf("%d\n", ans);
	} 
	return 0;
}