[APIO2008]免费道路-克鲁斯卡尔专精

失踪人口回归!!可惜回归后第一道题就是水题

题面

给定一张n个点m条边的无向图(不保证联通),其中图上每个边都有颜色(黑白),现在要构造一棵生成树,使树上黑边数量等于k,如果不存在合法的构造方案,输出"no solution"。

k < n ≤ 2 × 1 0 4 k<n \le 2\times 10^4 k<n2×104
m ≤ 1 0 5 m \le10^5 m105

题解

首先考虑什么时候会No solotion

  1. 图不连通
  2. k太大,比黑边总数还大
  3. k太小,以至于只用k条黑边无法使图联通

其中第一和第二种都很好判断,至于第三种,简单粗暴的方法是按白边构成的联通块缩点,看缩完的图的点数是否比k小(这里默认图是联通的),但是这个办法太繁琐了,如果熟练克鲁斯卡尔的话,很容易得出第二种方法。

第一遍克鲁斯卡尔,处理出必须连的黑边,即缩完点后的图上的生成树边,具体实现就是先把所有能加进生成树的白边加进去,再来扫黑边,这时加进去的黑边是必须连的。

然后第二遍克鲁斯卡尔,先把必须连的黑边连进去,然后再加入黑边,直到黑边数量达到k,然后加白边,这样同时就完成了构造。

这个思考过程我写的比较短,所以比较精髓,建议慢慢读。

放代码!

# include <bits/stdc++.h>
using namespace std;
namespace fastio{
	template<typename tn> void read(tn &a){
	    tn x=0,f=1;char c=' ';
	    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
	    for(;isdigit(c);c=getchar() ) x=x*10+c-'0';
	    a=x*f;
	}
	template<typename tn> void print(tn a){
	    if(a<0) putchar('-'),a=-a;
	    if(a>9) print(a/10);
	    putchar(a%10+'0');
	}
};
using namespace fastio;
const int N=2e4+5;
const int M=1e5+4;
int n,m,k;
struct Edge{
	int x,y;
}e0[M],e1[M],em[M];
int ct0,ct1,ctm,sum,tot;
int f[N];
int find(int x){
	if(f[x]==x) return x;
	return f[x]=find(f[x]);
}
int main(){
	read(n),read(m),read(k);
	for(int i=1;i<=m;i++){
		int x,y;
		bool b;
		read(x);
		read(y);read(b);
		if(b==0) e1[++ct1]=(Edge){x,y};
		else e0[++ct0]=(Edge){x,y};
	}
	for(int i=1;i<=n;i++) f[i]=i;
	for(int i=1;i<=ct0;i++){
		int fx=find(e0[i].x);
		int fy=find(e0[i].y);
		if(fx==fy) continue;
		f[fx]=fy;
		++sum;
	}
	for(int i=1;i<=ct1;i++){
		int fx=find(e1[i].x);
		int fy=find(e1[i].y);
		if(fx==fy) continue;
		f[fx]=fy;
		em[++ctm]=e1[i];
		++sum;
	}
	if(sum<n-1||ctm>k||ct1<k){
		puts("no solution");
		return 0;
	}
	for(int i=1;i<=n;i++) f[i]=i;
	for(int i=1;i<=ctm;i++){
		int fx=find(em[i].x);
		int fy=find(em[i].y);
		printf("%d %d 0\n",em[i].x,em[i].y);
		f[fx]=fy;
	}
	tot=ctm;
	for(int i=1;tot<k;i++){
		int fx=find(e1[i].x);
		int fy=find(e1[i].y);
		if(fx==fy) continue;
		f[fx]=fy;
		printf("%d %d 0\n",e1[i].x,e1[i].y);
		tot++;
	}
	for(int i=1;i<=ct0;i++){
		int fx=find(e0[i].x);
		int fy=find(e0[i].y);
		if(fx==fy) continue;
		f[fx]=fy;
		printf("%d %d 1\n",e0[i].x,e0[i].y);
	}
	return 0;
}

话说我时至今日,写到这道题时才幡然大悟:原来cnt是count的意思!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值