用于求解具有单调性的从一堆物品中恰好选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;
}
标签:二分,int,WQS,mid,mw,权值,need,100
From: https://www.cnblogs.com/safeng/p/16889816.html