当前位置: 代码迷 >> 综合 >> HOJ 5627 Clarke and MST(最小生成树变形,经典)
  详细解决方案

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

热度:46   发布时间:2023-12-13 19:04:55.0

最小生成树变形,经典
题目意思:
有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 */