WQS二分

用于求解具有单调性的从一堆物品中恰好选\(need\)个且满足权值最大/小化等要求的问题。

无向带权连通图,每条边是黑色或白色。求一棵最小权的恰好有\(need\)条白色边的生成树。

二分给白边加上的权值,加的权值越大,最小生成树中白边数量越少,明显具有单调性。

#include<bits/stdc++.h>

using namespace std;

const int N=1e5+5;
struct edge{
    int x,y,w,c;
    inline friend bool operator<(const edge&a,const edge&b){
        return a.w==b.w?a.c<b.c:a.w<b.w;//优先选白边
    }
}e[N];
int f[N],x[N],y[N],w[N],c[N],tot,cnt,n,m,need,sum;
inline int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
inline bool check(int k){
    tot=cnt=0;
    for(int i=1;i<=n;i++)f[i]=i;
    for(int i=1;i<=m;i++)e[i]={x[i],y[i],w[i]+(c[i]^1)*k,c[i]};//白边,加上k
    sort(e+1,e+1+m);//kruscal最小生成树
    for(int i=1;i<=m;i++){
        int a=find(e[i].x),b=find(e[i].y);
        if(a!=b){
            f[a]=b;
            tot+=e[i].w;
            if(!e[i].c)cnt++;//有一条白边被选到了生成树里面
        }
    }
    return cnt>=need;
}
signed main(){
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>n>>m>>need;
    for(int i=1;i<=m;i++){
        cin>>x[i]>>y[i]>>w[i]>>c[i];
        x[i]++;y[i]++;//点是从0开始编号的
    }
    int l=-100,r=100;
    while(l<=r){
        int mid=l+r>>1;//二分给白边加上的权值
        if(check(mid))l=mid+1,sum=tot-need*mid;
        else r=mid-1;
    }
    cout<<sum;
    return 0;
}

写法上一个优化,将白边与黑边分开排序,这样在跑最小生成树时不用再次排序,用类似归并排序的方法扫一遍即可

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+9,M=1e6+9;
struct Edge{
	int x,y,w;bool c;
	inline bool operator<(const Edge&x)const{return w<x.w;}
}e[M];
int f[N],n,m,need;
inline int find(int x){return x==f[x]?x:f[x]=find(f[x]);}
inline bool add(int i){//尝试加边并返回是否成功
	if(find(e[i].x)==find(e[i].y))return 0;
	f[f[e[i].x]]=f[e[i].y];return 1;
}
int main(){
    cin>>n>>m>>need;
	int mw=0,i,j;
	for(i=0;i<m;++i){//点和边都从0开始存了
		cin>>e[i].x>>e[i].y>>e[i].w>>e[i].c;
		if(!e[i].c)swap(e[mw++],e[i]);//将黑白边分开
	}
	sort(e,e+mw);sort(e+mw,e+m);
	int l=-100,r=100,mid,ans;
	while(l<r){//神奇的二分函数
		mid=(l+r+1)>>1;
		for(i=0;i<n;++i)f[i]=i;
		ans=i=0;j=mw;
		while(i<mw&&j<m)e[i].w+mid<=e[j].w?ans+=add(i++):add(j++);//类归并排序
		while(i<mw)ans+=add(i++);//白边数量统计完整
		ans<need?r=mid-1:l=mid;
	}
	mid=l;
	for(i=0;i<n;++i)f[i]=i;
	ans=i=0;j=mw;//最后算权值总和
	while(i<mw&&j<m)if(e[i].w+mid<=e[j].w)ans+=(e[i].w+mid)*add(i),++i;else ans+=e[j].w*add(j),++j;
	while(i<mw)ans+=(e[i].w+mid)*add(i),++i;
	while(j<m )ans+=e[j].w*add(j),++j;
	cout<<(ans-need*mid);//减掉
	return 0;
}
posted @ 2022-11-14 17:51  半步蒟蒻  阅读(49)  评论(0)    收藏  举报