判断最小生成树的唯一性

该博客探讨了如何在图中确保最小生成树的唯一性。通过删除权值相同并能合并相同连通块的多余边,可以实现这一目标。文章详细介绍了算法实现过程,包括Kruskal算法的变种,以及如何检查一个图的最小生成树是否唯一。此外,还提供了两种不同的代码实现来解决这个问题,时间复杂度为O(n+m)。
摘要由CSDN通过智能技术生成

先来看一个例题:Forsaken喜欢独一无二的树

题意:
现在给定一个 n n n 个点, m m m 条边的图,每条边 e i e_{i} ei 都有一个权值 w i w_{i} wi

刚开始最小生成树可能不唯一,现在可以删除一些边,使得剩下的边的最小生成树大小不变并且唯一。

求删除的边的权值和最小是多少?

分析:
什么样的边会影响到最小生成树的唯一性呢?

kruskal 求最小生成树 是将所有边权从小到大排序,然后判断当前边的两个端点所在连通块是否连通。如果没有连通,那么这条边就需要拿。
而此时如果有另外一条边,虽然也可以将这两个连通块合并,但是其权值比较大,那么这条边是不会影响到最小生成树的。
所以,只有两个边的权值相同,并且都能将端点的两个连通块合并,这么这两个边选择哪个都行,那么最小生成树就不唯一了

所以,为了保证最小生成树唯一,那么就是要去掉若干条 权值相同并且能够合并相同连通块的边,只剩一个这样的边就行。

如何实现呢?

我们像 kruskal 一样将所有的边按照权值排序,从小到大遍历所有的边。
对于当前边来说,将所有的和当前边权相等的边都拿过来(双指针)。对于这些权值相同的边,我们要去掉一些能够合并相同连通块的边。

我们可以先将所有的权值相同且能够合并连通块的边都删除,然后再留下最小生成树中的边。
这样,对于权值相同的能够合并连通块的多余边就被删除了。

  • 先遍历所有边,判断其是否可选,也就是判断其端点连接的两个连通块是否已经连通。如果没有连通,说明可选,删除的权值和 ans += wi
    这时,我们把能够合并连通块的所有边都删除了。
  • 然后,再遍历一遍,用这些边求最小生成树权值和 sum

最终,删除的多余边权之和就为 ans - sum

	int sum = 0;
	for(int i=1;i<=m;i++)
	{
		int r = i;
		
		while(r <= m && a[r].w == a[i].w) r++;
		r--;
		
		for(int j=i;j<=r;j++)
		{
			int x = a[j].x, y = a[j].y, w = a[j].w;
			if(find(x) != find(y)) ans += w;
		}
		
		for(int j=i;j<=r;j++)
		{
			int x = a[j].x, y = a[j].y, w = a[j].w;
			if(find(x) != find(y)) pre[find(x)] = find(y), sum += w;
		}
		
		i = r;
	}
	
	ans -= sum;

完整Code:

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define mem(a,b) memset(a,b,sizeof a)
#define int long long
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'
map<PII,int> mp;

/**/

const int N = 200010, mod = 1e9+7;
int T, n, m;
struct node{
	int x, y, w;
}a[N];
int ans, pre[N];

bool cmp(node a, node b){
	return a.w < b.w;
}

int find(int x){
	if(pre[x] != x) pre[x] = find(pre[x]);
	return pre[x];
}

void kruskal()
{
	sort(a+1, a+m+1, cmp);
	
	int sum = 0;
	for(int i=1;i<=m;i++)
	{
		int r = i;
		
		while(r <= m && a[r].w == a[i].w) r++;
		r--;
		
		for(int j=i;j<=r;j++)
		{
			int x = a[j].x, y = a[j].y, w = a[j].w;
			if(find(x) != find(y)) ans += w; //能拿的都删去
		}
		
		for(int j=i;j<=r;j++)
		{
			int x = a[j].x, y = a[j].y, w = a[j].w;
			if(find(x) != find(y)) pre[find(x)] = find(y), sum += w; //求最小生成树
		}
		
		i = r;
	}
	
	ans -= sum; //把最小生成树的权值留下,删去的就是多余的了
}

signed main(){
	Ios;
	cin>>n>>m;
	
	for(int i=1;i<=n;i++) pre[i] = i;
	
	for(int i=1;i<=m;i++)
	{
		int x, y, w;cin>>x>>y>>w;
		a[i] = {x, y, w};
	}
	
	kruskal();
	
	cout << ans;
	
	return 0;
}

这样,阻碍最小生成树唯一的边就都被删去了。

那么,如果需要判断一个图中最小生成树是否唯一,那么就可以用这种方法,看最终的删去边的权值是否为0,如果为0就是唯一的。
或者,也可以对于每一种权值来说,判断这其中的 可拿的边(端点所在连通块不连通) 是不是都在最小生成树中,如果有的不在,则说明最小生成树不唯一。

因为既然一个边可拿,也就是端点所在连通块不连通,那么其就应该出现在最小生成树中。而现在第二遍遍历跑最小生成树之后,发现之前可拿边的个数和可拿边的个数不同,那么也就是有多余的边,最小生成树不唯一。

例题:The Unique MST

Code:
#include<iostream>
#include<algorithm>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define mem(a,b) memset(a,b,sizeof a)
#define int long long
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'

/**/

const int N = 200010, mod = 1e9+7;
int T, n, m;
struct node{
	int x, y, w;
}a[N];
int ans, pre[N];
int sum;

bool cmp(node a, node b){
	return a.w < b.w;
}

int find(int x){
	if(pre[x] != x) pre[x] = find(pre[x]);
	return pre[x];
}

int kruskal()
{
	sort(a+1, a+m+1, cmp);
	
	for(int i=1;i<=m;i++)
	{
		int r = i;
		
		while(r <= m && a[r].w == a[i].w) r++;
		r--;
		
		int cnt = 0; //记录可拿边的个数 
		for(int j=i;j<=r;j++)
		{
			int x = a[j].x, y = a[j].y, w = a[j].w;
			if(find(x) != find(y)) cnt++; //可拿边个数++ 
		}
		
		for(int j=i;j<=r;j++)
		{
			int x = a[j].x, y = a[j].y, w = a[j].w;
			if(find(x) != find(y)) pre[find(x)] = find(y), sum += w, cnt --; //用掉了,cnt-- 
		}
		
		i = r;
		
		if(cnt) return 0; //最后还剩余可拿边,最小生成树不唯一 
	}
	return 1;
}

signed main(){
	Ios;
	cin>>T;
	while(T--)
	{
		cin>>n>>m;
		
		ans = 0, sum = 0;
		for(int i=1;i<=n;i++) pre[i] = i;
		
		for(int i=1;i<=m;i++)
		{
			int x, y, w;cin>>x>>y>>w;
			a[i] = {x, y, w};
		}
		
		if(!kruskal()) cout<<"Not Unique!\n";
		else cout << sum <<endl;
	}
	
	return 0;
}

这种做法时间复杂度和求最小生成树复杂度相同,O(n+m)。
感觉比求次小生成树简单呢~

  • 11
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值