BZOJ 2654 tree【二分+Kruskal】

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;
}


猜你喜欢

转载自blog.csdn.net/niiick/article/details/81317679
今日推荐