bzoj2654 tree(二分+kruskal)

问题 D: tree

题目描述

给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有need条白色边的生成树。
题目保证有解。

输入

第一行V,E,need分别表示点数,边数和需要的白色边数。
接下来E行,每行s,t,c,col表示这边的端点(点从0开始标号),边权,颜色(0白色1黑色)。

输出

一行表示所求生成树的边权和。
V<=50000,E<=100000,所有数据边权为[1,100]中的正整数。

样例输入

2 2 1
0 1 1 1
0 1 2 0

样例输出

 2

完全没意识到要二分答案。。。
二分一个权值x,给白边边权全部加上x,根据kruskal的过程,部分白边会被卡掉;当白边数大于need时,提高下界;
反之降低上界。
注意:边权值和不能在二分答案中由(白边边权-x)+黑边边权来统计,因为会出现这种情况:二分出的mid所选出的白边数>need,mid+1所选出的白边数<need,这时可以在排序时优先按权值排序,权值相同时按颜色排序,当出现如上情况时,把白边当做黑边看,所以ans=sum-need*x。
代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define maxn 50005
#define maxx 100005
using namespace std;
inline int read()
{   char c=getchar();int x=0,y=1;
    while(c<'0'||c>'9'){if(c=='-') y=-1;c=getchar();}
    while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x*y;
}
int n,m,nd,num,nnum,fa[maxn],ans,sum,lef,rig,all;
inline int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
inline void unionn(int x,int y){fa[x]=y;}
struct road{int be,en,w,col;}ro[maxx],nro[maxx];
bool cmp(road x,road y){return x.w==y.w?x.col<y.col:x.w<y.w;}
void add(int x,int y,int z,int v){ro[++num].be=x;ro[num].col=v;ro[num].en=y;ro[num].w=z;}
void addv(int x,int y,int z,int v){nro[++nnum].be=x;nro[nnum].col=v;nro[nnum].en=y;nro[nnum].w=z;}
bool check(int x)
{   int k=0;all=0;sum=0;nnum=0;
    for(int i=1;i<=n;++i) fa[i]=i;
    for(int i=1;i<=num;++i)
        if(!ro[i].col) addv(ro[i].be,ro[i].en,ro[i].w+x,ro[i].col);
        else addv(ro[i].be,ro[i].en,ro[i].w,ro[i].col);
    sort(nro+1,nro+nnum+1,cmp);
    for(int i=1;i<=nnum;++i)
    {   int a=find(nro[i].be),b=find(nro[i].en);
        if(a!=b)
        {   unionn(a,b);
            ++k;sum+=nro[i].w;
            if(!nro[i].col) ++all;
        }
        if(k==n-1) break;
    }
    if(all>=nd) return 1;
    return 0;
}
int main()
{   //freopen("nt2012_tree.in","r",stdin);
    //freopen("nt2012_tree.out","w",stdout);
    n=read();m=read();nd=read();
    int x,y,z,v;
    for(int i=1;i<=m;++i)
    {   x=read()+1;y=read()+1;z=read();v=read();
        add(x,y,z,v);
    }
    lef=-105;rig=105;
    while(lef<=rig)
    {   int mid=lef+rig>>1;
        if(check(mid)) ans=sum-nd*mid,lef=mid+1;
        else rig=mid-1;
    }
    printf("%d",ans);
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>