题目描述
传送门/bzoj2654
分析
考虑如何才能让白边显得更(不)重要,即在每条白边上(加上)减去一个值。
我们可以二分这个值,然后做最小生成树。统计在此最小生成树里有多少白边。
然后我们就可以找到一个合适的值,带这个权再做一次最小生成树。
在计算答案的时候把这些值补偿回去就做完了。
怎样保证正好k条白边呢?
先加k条最小的白边,再加k-n-1条黑边…….
不会这么简单!贪心肯定是不可以的
那么考虑曾经做过的一道网络流:统计最大流,并要求边数最少
当时是边权乘上一个极大值后加一直接上最大流
类比一下
不妨改变白边权(偏移量)使其最小生成树上正好有k条白边……
恩……二分应运而生。
notice:
一定要在最后把这些值补充回去,不能够在最小生成树统计答案时补偿答案,我也不知道为什么……
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#define N 50100
int n,m,k,cnt=0,ans;
int fa[N];
struct Node{
int u,v,w,d;
}e[N*4],s[N*4];
void add(int u,int v,int w,int d){
s[++cnt].u=u;s[cnt].v=v;s[cnt].w=w;s[cnt].d=d;
}
int cmp(Node a,Node b){
if(a.w==b.w) return a.d<b.d;
return a.w<b.w;
}
int find(int x){
return fa[x]==x?x:fa[x]=find(fa[x]);
}
int kruskal(int mid){
int num=0,bb_num=0,x,y,w,d,tx,ty;ans=0;
memcpy(e,s,sizeof(s));
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=m;i++)
if(!e[i].d) e[i].w+=mid;
sort(e+1,e+m+1,cmp);
for(int i=1;i<=m;i++){
x=e[i].u;y=e[i].v;w=e[i].w;d=e[i].d;
tx=find(x);ty=find(y);
if(tx!=ty){
if(!d) bb_num++;
fa[tx]=ty;
ans+=w;
num++;
}
if(num==n-1) break;
}
return bb_num;
}
int main(){
// freopen("tree.in","r",stdin);
// freopen("tree.out","w",stdout);
scanf("%d%d%d",&n,&m,&k);
int a,b,c,d;
for(int i=1;i<=m;i++){
scanf("%d%d%d%d",&a,&b,&c,&d);
a++;b++;add(a,b,c,d);
}
int l=-101,r=101,mid,numb;
while(l<r){
mid=(l+r+1)/2;
numb=kruskal(mid);
if(numb>=k) l=mid;
else r=mid-1;
}
kruskal(l);
printf("%d",ans-k*l);
return 0;
}