BZOJ2654 tree 生成树+二分法

题意:给定一张无向图,图中每条边均为白色或黑色,求有K个白色节点的边权和最小的生成树,数据保证有解

题解:

比较直观的想法是跑一个最小生成树,然后不断换边使得白边的数量等于K

然而我们可以给所有白边加上一个值,使其被选的几率增大或减小,而赋的值可以通过二分来找到。

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;

const int MAXM=100000+2;
struct EDGE{
    int u,v,w,c;
}e[MAXM];
int N,M,K,ans,tot,u[MAXM],v[MAXM],w[MAXM],c[MAXM],f[MAXM];

bool cmp(EDGE a,EDGE b){ return a.w==b.w?a.c<b.c:a.w<b.w;}

int Find(int x){ return x==f[x]?x:f[x]=Find(f[x]);}

bool Kruskal(int x){
    int cnt=0;tot=0;
    for(int i=1;i<=N;i++)f[i]=i;
    for(int i=1;i<=M;i++){
        e[i].u=u[i],e[i].v=v[i],e[i].w=w[i];e[i].c=c[i];
        if(!c[i]) e[i].w+=x;
    }
    sort(e+1,e+M+1,cmp);

    for(int i=1;i<=M;i++){
        int p=Find(e[i].u),q=Find(e[i].v);
        if(p!=q){
            f[p]=q,tot+=e[i].w;
            if(!e[i].c) cnt++;
        }
    }

    return cnt>=K;
}

int main(){
    scanf("%d %d %d",&N,&M,&K);
    for(int i=1;i<=M;i++){
        scanf("%d %d %d %d",u+i,v+i,w+i,c+i);
        u[i]++;v[i]++;
    }

    int l=-105,r=105;
    while(l<=r){
        int m=(l+r)>>1;
        if(Kruskal(m)) l=m+1,ans=tot-K*m;
        else r=m-1;
    }
    printf("%d",ans);

    return 0;
}
View Code

 

posted @ 2017-02-27 23:14  WDZRMPCBIT  阅读(181)  评论(0编辑  收藏  举报