2018.09.25【BZOJ2654】Tree (最小生成树)(二分法)

传送门


解析:

乍一看完全没思路的一道题。
搜索?显然不行。。。
然而,看出正解的我无f**k说。。。

思路:

考虑怎么调整白边的个数。在做最小生成树的时候。

我们可以通过给白边全部加上或减去一个权值来搞定。

加上一个数,可以使白边在生成树里面的条数减少。
加少一点,就会使白边在生成树里面的条数增加。
这个是很显然的。并且是对白色边的条数影响是具有单调性的。

但是问题就来了,怎么知道我们这样做是对的?

严格证明我就不放了,敲起来有点麻烦,有需要的在评论区评论并点赞,人多了我就更新严格证明。(应该没人吧,毕竟这次的感性理解还是比较清楚的

下面放感性理解。

把黑色边和白色边分别放在两个排好序的队列里面(woc,就不用排序那么多次了,我写代码的时候怎么没想到,应该比直接排序更快啊。。。)

然后做 k r u s k a l kruskal kruskal生成树的时候显然就是直接在两个队首取。
把白色加上或减去一个权值,就会直接影响有几个黑色会在它前面被取出,

我们可以通过这种方式调整白色出来的次数。

那么显然,这样就是对的。


代码(全部排序):

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const

inline
int getint(){
	re int num;
	re char c;
	while(!isdigit(c=gc()));
	num=c^48;
	while(isdigit(c=gc()))num=(num<<1)+(num<<3)+(c^48);
	return num;
}

inline
void outint(int a){
	static char ch[13];
	if(a==0)pc('0');
	while(a)ch[++ch[0]]=a-a/10*10,a/=10;
	while(ch[0])pc(ch[ch[0]--]^48);
}

struct edge{
	int u,v,w;
	bool col;
	edge(){}
	edge(int u,int v,int w,bool col):u(u),v(v),w(w),col(col){}
	friend bool operator<(cs edge &a,cs edge &b){
		return a.w==b.w?a.col<b.col:a.w<b.w;
	} 
}e[100002];

int n,m,need;

int fa[50002];
inline
int getfa(int x){
	return x==fa[x]?x:fa[x]=getfa(fa[x]);
}

inline
void merge(int i,int j){
	i=getfa(i);
	j=getfa(j);
	fa[i]=j;
}

int weight;
inline
bool check(int mid){
	for(int re i=1;i<=n;++i){
		fa[i]=i;
	}
	for(int re i=1;i<=m;++i){
		if(!e[i].col)e[i].w+=mid;
	}
	sort(e+1,e+m+1);
	int cnt=0,tot=0;
	weight=0;
	for(int re i=1;i<=m;++i){
		int u=getfa(e[i].u),v=getfa(e[i].v);
		if(u==v)continue;
		++tot;
		merge(u,v);
		weight+=e[i].w;
		if(!e[i].col){
			++cnt;
		}
		if(tot==n-1)break;
	}
	
	for(int re i=1;i<=m;++i){
		if(!e[i].col)e[i].w-=mid;
	}
	return cnt>=need;
}

signed main(){
	n=getint(),m=getint(),need=getint();
	for(int re i=1;i<=m;++i){
		int u=getint()+1,v=getint()+1,w=getint(),col=getint();
		e[i]=edge(u,v,w,col);
	}
	int l=-100,r=100;
	int ans;
	while(l<=r){
		int mid=(l+r)>>1;
		if(check(mid))l=mid+1,ans=weight-need*mid;
		else r=mid-1;
	}
	cout<<ans<<endl;
	return 0;
}

代码(双队列)有问题,但是暂时不想改:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const

inline
int getint(){
	re int num;
	re char c;
	while(!isdigit(c=gc()));
	num=c^48;
	while(isdigit(c=gc()))num=(num<<1)+(num<<3)+(c^48);
	return num;
}

inline
void outint(int a){
	static char ch[13];
	if(a==0)pc('0');
	while(a)ch[++ch[0]]=a-a/10*10,a/=10;
	while(ch[0])pc(ch[ch[0]--]^48);
}

struct edge{
	int u,v,w;
	edge(){}
	edge(int u,int v,int w):u(u),v(v),w(w){}
	friend bool operator<(cs edge &a,cs edge &b){
		return a.w<b.w;
	} 
}bl[100002],wh[100002];
int b,h;
int n,m,need;

int fa[50002];
inline
int getfa(int x){
	return x==fa[x]?x:fa[x]=getfa(fa[x]);
}

inline
void merge(int i,int j){
	i=getfa(i);
	j=getfa(j);
	fa[i]=j;
}

int weight;
inline
bool check(int mid){
	for(int re i=1;i<=n;++i){
		fa[i]=i;
	}
	int bhead=1,whead=1;
	int cnt=0,tot=0;weight=0;
	while(bhead<=b||whead<=h){
		if((bhead<=b&&bl[bhead].w<wh[whead].w+mid)||whead>h){
			edge e=bl[bhead++];
			int u=getfa(e.u),v=getfa(e.v);
			if(u==v)continue;
			merge(u,v);
			++tot;
			weight+=e.w;
		}
		else{
			edge e=wh[whead++];
			int u=getfa(e.u),v=getfa(e.v);
			if(u==v)continue;
			merge(u,v);
			++tot;
			++cnt;
			weight+=e.w;
		}
		if(tot==n-1)break;
	}
	return cnt>=need;
}

signed main(){
	n=getint(),m=getint(),need=getint();
	for(int re i=1;i<=m;++i){
		int u=getint()+1,v=getint()+1,w=getint(),col=getint();
		if(col)bl[++b]=edge(u,v,w);
		else wh[++h]=edge(u,v,w); 
	}
	sort(bl+1,bl+b+1);
	sort(wh+1,wh+h+1); 
	int l=-100,r=100;
	while(l<r){
		int mid=(l+r+1)>>1;
		if(check(mid))l=mid;
		else r=mid-1;
	}
	cout<<weight<<endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值