洛谷P2619 [国家集训队]Tree I(二分+最小生成树)
https://www.luogu.com.cn/problem/P2619
边有黑白两色,求恰好有k条白边的最小生成树
在克鲁斯卡尔算法中,将边权从小到大排序
我们可以通过将白边的权值加减,来改变白边在排序中的位置
假设要求白边用5条,现在是所有白边权值加3
若求出来白边用了8条,说明权值加小了,白边在前面太多,需要加更多的权值,把一些白边扔到后面
若求出来白边用了2条,说明权值加大了,前面的白边太少,需要加小些的权值,把一些白边扔到前面
这样就可以二分进行加权值的调整
若找到了一个权值,加上之后,白边恰好用了要求的条数,皆大欢喜
若加权值x,白边使用条数大于要求条数;然而加权值x+1,导致白边使用条数小于要求条数
这种情况下,存在黑边与加权之后的白边权值相同,这样把白边换成黑边即可
所以二分过程中,只要使用条数大于等于要求条数,就更新一次答案
#include<bits/stdc++.h> using namespace std; #define N 50001 #define M 100001 int n,m,k; struct node { int u,v,w,c; }e[M]; int use,sum; int fa[N]; bool cmp(node p,node q) { if(p.w!=q.w) return p.w<q.w; return p.c<q.c; } int find(int i) { return fa[i]==i ? i : fa[i]=find(fa[i]); } void check(int x) { for(int i=1;i<=m;++i) if(!e[i].c) e[i].w+=x; sort(e+1,e+m+1,cmp); for(int i=1;i<=n;++i) fa[i]=i; int now=0,fu,fv,j=1; use=sum=0; while(now!=n-1) { fu=find(e[j].u); fv=find(e[j].v); if(fu!=fv) { now++; fa[fu]=fv; if(!e[j].c) ++use; sum+=e[j].w; } ++j; } for(int i=1;i<=m;++i) if(!e[i].c) e[i].w-=x; } int main() { scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=m;++i) { scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].w,&e[i].c); ++e[i].u; ++e[i].v; } int l=-101,r=101,mid,ans; while(l<=r) { mid=l+r>>1; // printf("%d ",mid); check(mid); if(use>=k) { // printf("\n"); ans=sum-k*mid; l=mid+1; } else r=mid-1; } printf("%d",ans); }