【思维/克鲁斯卡尔算法/树】P3623 [APIO2008] 免费道路

题意

原题,此处提供题意简述。

n ( 1 ≤ n ≤ 2 × 1 0 4 ) n(1\leq n\leq 2\times10^4) n(1n2×104) 个节点 m ( 1 ≤ m ≤ 1 0 5 ) m(1\leq m\leq 10^5) m(1m105) 条双向边,每条边的边权都是 0 0 0 1 1 1。求能否构造出一个生成树使得该生成树中边权是 0 0 0 的边的数量一共有 k k k 条,若能输出方案,若不能输出 no solution

代码

转换题意,一共 n − 1 n-1 n1 条边,需要有 k k k 条边权为 0 0 0 边,则有 n − k − 1 n-k-1 nk1 条边权是 1 1 1 的边。

我们首先按照边权从小到大连边,也就是优先连边权是 0 0 0 的边,用 u s e d used used 统计在该方案下所需最少的边权是 1 1 1 的边。这个步骤有 2 个目的:

  • 确保运用 n − k − 1 n-k-1 nk1 条边权是 1 1 1 的边能够连接整张图。
  • 确保除去这 u s e d used used 条边后剩下的图能够用边权是 0 0 0 的图联通。

如果 u s e d > n − k − 1 used > n-k-1 used>nk1,也就是必要加的边权是 1 1 1 的边条数大于规定,直接输出 no solution

接着,我们用部分边权是 1 1 1 的边替换边权是 0 0 0 的边,使得边权是 1 1 1 的边条数符合规定。如果找不到那么多边权是 1 1 1 的边,同样输出 no solution。由于我们第一次跑克鲁斯卡尔算法的时候是优先加边权是 0 0 0 的边,这保证剩下的图能够用边权是 0 0 0 的图联通,因此部分替换后仍然可以用边权是 0 0 0 的边连接使得整张图联通成生成树。

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,k;
struct edge{
	int x,y,w;
	bool nec = 0;//是否是必须要加入 
}node[100005]; 
//从小到大排序 
bool cmp(edge a,edge b) {
	return a.w < b.w;
}
bool cmp1(edge a,edge b) {
	return a.w > b.w;
}
int head[100005];
int find(int x) {
	return head[x] == x?x:head[x] = find(head[x]);
}
int used = 0,lian = 0;
signed main() {
	scanf("%lld %lld %lld",&n,&m,&k);
	k = n - 1 - k;
	for(int i = 1;i <= m;i++) {
		scanf("%lld %lld %lld",&node[i].x,&node[i].y,&node[i].w);
	}
	sort(node + 1,node + m + 1,cmp);//从小到大排序 
	for(int i = 1;i <= n;i++) head[i] = i;
	for(int i = 1;i <= m;i++) {
		int p = find(node[i].x),q = find(node[i].y);
		if(p != q) {
			head[q] = p;
			lian++;
			if(node[i].w == 1) used++,node[i].nec = 1;
		} 
	} 
	if(used > k or lian != n - 1) {
		printf("no solution\n");
		return 0;
	}
	for(int i = 1;i <= n;i++) head[i] = i;
	sort(node + 1,node + m + 1,cmp1);
	for(int i = 1;i <= m;i++) {
		if(node[i].nec) {
			head[find(node[i].y)] = find(node[i].x);
		}
	}
	for(int i = 1;i <= m;i++) {
		if(node[i].nec) continue;
		int p = find(node[i].x),q = find(node[i].y);
		if(used < k and node[i].w == 0) {
			printf("no solution\n");
			return 0;
		}
		if(used == k and node[i].w == 1) continue;
		if(p == q) continue;
		head[q] = p,node[i].nec = 1;
		if(node[i].w == 1) used++;
	}
	for(int i = 1;i <= m;i++) {
		if(node[i].nec == 1) printf("%lld %lld %lld\n",node[i].x,node[i].y,node[i].w);
	}
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值