二分+Kruskal - Tree(BZOJ 2654)

描述
给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有need条白色边的生成树。 题目保证有解。

输入
第一行V,E,need分别表示点数,边数和需要的白色边数。

接下来E行

每行s,t,c,col表示这边的端点(点从0开始标号),边权,颜色(0白色1黑色)

输出
一行表示所求生成树的边权和。

样例输入 [复制]
2 2 1
0 1 1 1
0 1 2 0
样例输出 [复制]
2
提示
数据规模和约定 ·10%:V<=10

30%:V<=15

100%:V<=50000,E<=100000

所有数据边权为[1,100]中的正整数。


Analysis

最小生成树无法控制白边的选取数量
于是我们就对白边增加/减少一定的值x
然后做Kruskal,记录白边的值
如果选取的数量大于need说明白边多了,则增加x(少选白边)
小于need说明白边少了,则减少x(多选白边)
如果刚好等于need,我们选择增加x
因为在能保证白边选取数量为need的时候,我们所选择的白边实际上已经固定
当x增加,白边相当于整体向后挪动,这样就可以使选择的黑边尽可能小
这样一来,我们选择的黑边是最小的,白边也是最小的,结果自然最优

ps在排序的时候边权相同时优先选择白边(和上述理解类似,请读者自行解决)


Code

(数组开小……很愉快)

#include<bits/stdc++.h>
#define in read()
#define re register
using namespace std;
inline int read(){
	char ch;int f=1,res=0;
	while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
	while(ch>='0'&&ch<='9'){
		res=(res<<1)+(res<<3)+(ch^48);
		ch=getchar();
	}
	return f==1?res:-res;
}
const int N=5e4+10,M=2e5+10;
int n,m,need,fa[N],tot;
int s[M],t[M],c[M],w[M];
struct node{int u,v,w,c;}e[M];
inline bool cmp(const node &a,const node &b){return a.w<b.w||(a.w==b.w&&a.c<b.c);}
inline int getfa(int x){
	return x==fa[x]?x:fa[x]=getfa(fa[x]);
}
inline bool check(int x){
	tot=0;int cnt=0;
	for(re int i=0;i<=n;++i) fa[i]=i;
	for(re int i=1;i<=m;++i){
		e[i].u=s[i];e[i].v=t[i];
		e[i].w=w[i];e[i].c=c[i];
		if(!c[i]) e[i].w+=x;
	}
	sort(e+1,e+m+1,cmp);
	for(re int i=1;i<=m;++i){
		int fu=getfa(e[i].u),fv=getfa(e[i].v);
		if(fu!=fv){
			fa[fu]=fv;
			tot+=e[i].w;
			if(!e[i].c) cnt++;
		}
	}
	return cnt>=need;
}
int main(){
	n=in;m=in;need=in;
	for(re int i=1;i<=m;++i){
		s[i]=in;t[i]=in;w[i]=in;c[i]=in;
	}
	int l=-105,r=105,ans;
	while(l<=r){
		int mid=l+r>>1;
		if(check(mid)) l=mid+1,ans=tot-need*mid;
		else r=mid-1;
	}
	cout<<ans;
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值