HOJ 5627 Clarke and MST(最小生成树变形,经典)

最小生成树变形,经典
题目意思:
有n个点,m条边。 然后问,求其中一个生成树,使得这个树的每一条边长度 全部 进行 与 运算,得到的结果最大。
其中,边长度 <= 1e9, 长度在 int 的范围。
本题要点:
1、int 有 32 位。
vector v[32]; // v[k] 存放的是边权 的第k位二进制是 1 的边的编号。
2、从最高位 k 开始, 如果 v[k].size() >= n - 1, 说明有 n - 1 条边,可能够成一个生成树,使得每一条边 进行 与
运算后,其值不为0; 容器 v[k] 里面装的所有边,进行最小生成树 kruskal 算法计算,看看是否能构成生成树。
3、 用一个 数组 vis[MaxM] 来统计有效边。
有效边,指的是, 从最高位开始 扫描, 存在一组 v[k] 里面的所有边,使得 kruskal 返回 true; 然后, 容器 v[k]
里面的所有边就是有效边。 当存在下一组 v[k1] ,也使得 kruskal 返回 true, 此时要更新有效边。
更新方法,就是在之前的有效边中,寻找该边权值的第 k1 位是 1 的边 新设置为 true,其他边设为 false;

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int MaxN = 300010, MaxM = 300010;
vector<int> v[32];
bool vis[MaxM];
int T, n, m;
int fa[MaxN];
int tmp[MaxN], cnt;
bool is_first;

struct res
{
	int x, y, z;
}edge[MaxM];

int get(int x)
{
	if(x == fa[x])
		return x;
	return fa[x] = get(fa[x]);
}

bool kruskal(int i)
{
	int len = v[i].size(), x, y;
	cnt = 0;
	for(int k = 0; k < len; ++k)
	{
		if(is_first || vis[v[i][k]])
		{
			tmp[cnt++] = v[i][k];// tmp 放的是边的编号	
		}
	}
	if(cnt == 0)	return false;
	for(int k = 0; k < cnt; ++k)
	{
		x = edge[tmp[k]].x, y = edge[tmp[k]].y;
		fa[x] = x, fa[y] = y;	//初始化
	}
	int add_edge = 0;
	for(int k = 0; k < cnt; ++k)
	{
		x = edge[tmp[k]].x, y = edge[tmp[k]].y;
		int fax = get(x), fay = get(y);
		if(fax == fay)	continue;
		++add_edge;
		fa[fax] = fay;
	}
	return add_edge >= n - 1;
}

void solve()
{
	is_first = true;
	for(int i = 0; i <= m; ++i)
		vis[i] = false;
	int ans = 0;
	for(int i = 31; i >= 0; --i)
	{
		if((int)v[i].size() < n - 1)	continue;
		if(kruskal(i))
		{
			int len = v[i].size();
			if(is_first)
			{
				is_first = false;
				for(int k = 0; k < len; ++k)
				{
					vis[v[i][k]] = true;
				}
			}else{
				int r = 1 << i;
				for(int k = 0; k < len; ++k)
				{
					if((edge[v[i][k]].z & r) == 0)
					{
						vis[v[i][k]] = false;
					}
				}
			}
			ans += 1 << i;		//答案累加
		}
	}
	printf("%d\n", ans);
}

int main()
{
	scanf("%d", &T);
	while(T--)
	{
		scanf("%d%d", &n, &m);
		for(int i = 1; i <= m; ++i)
		{
			scanf("%d%d%d", &edge[i].x, &edge[i].y, &edge[i].z);	
			for(int j = 0; j < 32; ++j)
			{
				int r = 1 << j;	
				if(edge[i].z & r)
					v[j].push_back(i);	// v存边的编号
			}
		}
		solve();
	}
	return 0;
}

/*
1
4 5
1 2 5
1 3 3
1 4 2
2 3 1
3 4 7
*/

/*
1
*/

猜你喜欢

转载自blog.csdn.net/qq_38232157/article/details/108423127