洛谷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); 
}

 

posted @ 2021-08-12 10:36  TRTTG  阅读(83)  评论(0编辑  收藏  举报