HDU 3367 Pseudoforest (最小生成树,并查集)

题目链接

题意
      题目先给出了伪森林的定义,伪森林首先是一个无向图,他的每一个连通分量最多只能包含一个环。然后一个伪森林的值是其所包含的所有的边的权值之和。题目输入一个无向图,要求输出该图所能生成的最大伪森林的值。图上有n个点,有m条边,每条边的两个端点分别为u和v,其权值为c。

数据范围的一些限定
0 < n <= 10000
0 <= m <= 100000
0 <= u,v < n
0 < c <= 10000

思路
      跟最小生成树算法的思想有点儿像,先对每一条边按照权值从大到小进行排序,然后每次取出剩余的最大的边,判断两个端点是否符合插入最大伪森林的条件(条件一会儿再说),如果符合,就插入伪森林并更新伪森林的值。
      要判断一条边的两个端点是否符合插入最大伪森林的条件,首先是要判断这两个端点是否属于同一个连通分量,这个很容易判断,跟最小生成树算法里的一样,用并查集实现,如果他们在并查集中具有相同的根,则他们属于同一个连通分量,否则他们不属于同一个连通分量。下一步是判断两个端点所在的连通分量是否具有环,只需要有一个变量标记其在并查集中的根即可,每个根都有,开个bool数组呗,要记得初始化。如果某条边的两个端点属于同一个连通分量,加上这条边,那么该连通分量必然会增加一个环,则在插入当前边前需要判断当前连通分量中是否有环,有环,则不能插入,无环,则插入并更新伪森林的值。如果这条边的两个端点不属于同一个连通分量,则需判断两个连通分量是否都具有环,若这两个连通分量中最多只有一个环,则可以插入这条边合并两个连通分量,更新伪森林的值,有环的时候还要更新伪森林的环标记,否则不能插入这条边。
      最后数据量略大,不能用cin,cout啊。。坑爹的我用cin和cout TLE了一次,然后换成scanf和printf就AC了。。

      以下是代码,各种多余头文件宏定义什么的请无视,在vimrc里写好了,懒得删掉了,可以视为我在装13。如果在其中发现有错误,请指出,发表此文部分原因是希望有人能够指出我可能存在的错误。 如果有人有更好的解法,请不吝赐教。
/*
 * Author:  Fiend
 * Created Time:  2013/4/14 1:27:35
 * File Name: test.cpp
 */
#include <iostream>
#include <cstdio>
#include <cstddef>
#include <cmath>
#include <algorithm>
#include <string>
#include <cstring>
#include <vector>
#include <bitset>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <cctype>

#define ST size_type
#define PB push_back
#define LL long long
#define MAXN 10005
#define MAXM 100005

using std::cin;
using std::cout;
using std::endl;
using std::string;
using std::bitset;
using std::vector;
using std::pair;
using std::swap;
using std::sort;
using std::max;
using std::min;

const int inf = 0x3fffffff;

typedef pair<int, int> pii;
typedef vector<int> vi;
typedef vector<int>::iterator vit;

struct edge {
    int u, v, c;

	//复习下自定义比较器,下面是按从大到小进行排序时用到的比较器
    friend bool operator < (const edge &a, const edge &b) {
        return a.c > b.c;
    }
} arr[MAXM]; //边的集合
int parent[MAXN];//保存并查集中每一个结点的树根
bool circled[MAXN];//标记连通分量是否存在环

//包括并查集的初始化和无环的初始化
void init (int n) {
	for (int i = 0; i < n; ++i) {
		parent[i] = i;
		circled[i] = false;
	}
}

//并查集找树根,路径压缩
int find (int x) {
	int i = x, r;
	while (parent[x] != x)
		x = parent[x];
	r = x;
	while (i != r) {
		x = parent[i];
		parent[i] = r;
		i = x;
	}
	return r;
}

int main () {
	int n, m, fa, fb, ans;
	while (scanf("%d%d", &n, &m), n || m) {
		for (int i = 0; i < m; ++i)
			scanf("%d%d%d", &arr[i].u, &arr[i].v, &arr[i].c);
		sort (arr, arr + m); //对所有的边按权值从大到小进行排序
		init (n);
		ans = 0;
		for (int i = 0; i < m; ++i) {
			fa = find (arr[i].u);
			fb = find (arr[i].v);
			if (fa == fb) {	//该边的两个端点属于同一个连通分量
				if (circled[fa])	//连通分量已存在环
					continue;
				circled[fa] = true;	//标记连通分量成环
				ans += arr[i].c;	//更新伪森林的值
			} else {	//该边的两个端点不属于同一个连通分量
				if (circled[fa] && circled[fb])	//两个连通分量都已存在环
					continue;
				parent[fa] = fb;	//并查集合并
				circled[fb] = circled[fb] || circled[fa]; //合并后的连通分量是否存在环
				ans += arr[i].c;	//更新伪森林的值
			}
		}
		printf("%d\n", ans);
	}
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值