Time Limit: 30 Sec
Memory Limit: 512 MB
Description
给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有need条白色边的生成树。
题目保证有解。
Input
第一行V,E,need分别表示点数,边数和需要的白色边数。
接下来E行,每行s,t,c,col表示这边的端点(点从0开始标号),边权,颜色(0白色1黑色)。
Output
一行表示所求生成树的边权和。
V<=50000,E<=100000,所有数据边权为[1,100]中的正整数。
题目分析
十分有意思的一道题,思路十分巧妙
二分一个权值给每条白边加上
然后跑一次生成树
若此时生成树中白边数>=need
则ans=sum-need*mid,L=mid
以此来减少下一次生成树中白边数量
否则R=mid吗,以增加生成树中白边数量
其实蒟蒻也还没完全理解这种做法的正确性
#include<iostream>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
int read()
{
int f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return f*x;
}
const int maxn=100010;
int n,m,k;
struct node{int u,v,dis,col;}E[maxn],edge[maxn];
bool cmp(node a,node b){return a.dis==b.dis?a.col<b.col:a.dis<b.dis;}
int fa[maxn];
int sum,ans;
int find(int x)
{
if(x==fa[x])return x;
else return fa[x]=find(fa[x]);
}
int kruskal(int x)
{
int num1=0,num2=0; sum=0;
for(int i=1;i<=n;++i)fa[i]=i;
for(int i=1;i<=m;++i)
{
edge[i].u=E[i].u;edge[i].v=E[i].v;edge[i].col=E[i].col;
if(!E[i].col) edge[i].dis=E[i].dis+x;
else edge[i].dis=E[i].dis;
}
sort(edge+1,edge+1+m,cmp);
for(int i=1;i<=m;++i)
{
int fu=find(edge[i].u),fv=find(edge[i].v);
if(fu!=fv)
{
fa[fu]=fv; num1++;
sum+=edge[i].dis;
if(!edge[i].col) num2++;
if(num1==n-1) break;
}
}
return num2>=k;
}
int main()
{
n=read();m=read();k=read();
for(int i=1;i<=m;++i)
E[i].u=read()+1,E[i].v=read()+1,
E[i].dis=read(),E[i].col=read();
int L=-110,R=110,mid;
while(L<R)
{
mid=L+R>>1;
if(kruskal(mid))L=mid+1,ans=sum-k*mid;
else R=mid;
}
printf("%d",ans);
return 0;
}