Submit: 2019 Solved: 825
[ Submit][ Status][ Discuss]
Description
给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有need条白色边的生成树。
题目保证有解。
Input
第一行V,E,need分别表示点数,边数和需要的白色边数。
接下来E行,每行s,t,c,col表示这边的端点(点从0开始标号),边权,颜色(0白色1黑色)。
Output
一行表示所求生成树的边权和。
V<=50000,E<=100000,所有数据边权为[1,100]中的正整数。
Sample Input
2 2 1
0 1 1 1
0 1 2 0
0 1 1 1
0 1 2 0
Sample Output
2
HINT
原数据出错,现已更新 by liutian,但未重测—2016.6.24
Source
无法证明算法的真确性(其实是可以证明的,只是太麻烦了,感性理解一下)
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 50005;
const int M = 100005;
struct data{
int x,y,w,flag;
inline bool operator < (const data &rhs)const{
return (w==rhs.w)?flag<rhs.flag:w<rhs.w;
}
}e[M<<2],a[M<<2];
int f[N],n,m,tot,k,l=-110,r=110,sum,ans;
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
inline int read(){
static char ch;int flag=1,res;
while((ch=getchar())<'0'||ch>'9')if(ch=='-')flag=-1;res=ch-48;
while((ch=getchar())>='0'&&ch<='9')res=res*10+ch-48;
return res*=flag;
}
bool judge(int x){
tot=sum=0;
for(register int i=1;i<=n;++i)f[i]=i;
for(register int i=1;i<=m;i++)
a[i].y=e[i].y,a[i].w=e[i].w+e[i].flag*x,a[i].x=e[i].x,a[i].flag=e[i].flag;
sort(a+1,a+1+m);
for(register int i=1;i<=m;i++){
if(find(a[i].x)!=find(a[i].y)){
f[find(a[i].x)]=find(a[i].y);
if(a[i].flag)++tot;
sum+=a[i].w;
}
}
return tot>=k;
}
int main(){
n=read(),m=read(),k=read();
for(register int i=1;i<=m;++i)
e[i].x=read()+1,e[i].y=read()+1,e[i].w=read(),e[i].flag=read()^1;
while(l<=r){
int mid=(l+r)>>1;
if(judge(mid))ans=sum-mid*tot,l=mid+1;
else r=mid-1;
}
printf("%d\n",ans);
return 0;
}