[bzoj2654][最小生成树][二分]tree

来源:互联网 发布:wkwebview 注入js代码 编辑:程序博客网 时间:2024/06/05 10:54

Description

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

Input

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

Output

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

Sample Input

2 2 1
0 1 1 1
0 1 2 0

Sample Output

2

题解

神题呐不会啊只能膜题解了。。
如果直接对原树进行kruskal的话,求出来的白边可能<need也可能>need的对不
那么怎么人为控制白边在树里的数目同时保证黑边选择最小呐?
还是最小生成树,只不过我们二分一个值mid,每次check的时候每条白边加上这个mid值,这样可以保证白边在生成树里一定是单调不下降或者单调不上升的,而这个时候的黑边同样有保证一定是最小的。
跑kruskal的时候判一下白边的边数,如果出来的边数>=need就是正确状态,那么树里的白边就需要减小,这时候mid往大的二分
小于need的情况就往小的二分
答案继承的时候需要减去mid*need这么多,因为白边至少都多了这么多的权嘛
一个特别的地方:题里可能有权相等的黑白边,这样kruskal排序的时候黑白边顺序是不定的。那这样跑出来的边数就可能小于need而wa掉。所以我们排序的时候当边权相等的时候再按颜色排,白色在前

#include<cstdio>#include<cstring>#include<cstdlib>#include<algorithm>#include<cmath>using namespace std;struct node{    int x,y,c,op;}a[211000],e[211000];int fa[111000];int findfa(int x){    if(fa[x]!=x)fa[x]=findfa(fa[x]);    return fa[x];}int n,m,nd,cnt;bool cmp(node n1,node n2){    if(n1.c!=n2.c)return n1.c<n2.c;    else return n1.op<n2.op;}bool chk(int p){    for(int i=1;i<=m;i++)    {        e[i]=a[i];        if(a[i].op==0)e[i].c+=p;    }    for(int i=1;i<=n;i++)fa[i]=i;    sort(e+1,e+1+m,cmp);    int tmp=n,op=0;cnt=0;    for(int i=1;i<=m;i++)    {        int p=findfa(e[i].x),q=findfa(e[i].y);        if(p!=q)        {            fa[p]=q;            cnt+=e[i].c;tmp--;            if(e[i].op==0)op++;            if(tmp==1)break;        }    }    if(op>=nd)return true;    else return false;}int main(){    scanf("%d%d%d",&n,&m,&nd);    for(int i=1;i<=m;i++){scanf("%d%d%d%d",&a[i].x,&a[i].y,&a[i].c,&a[i].op);a[i].x++;a[i].y++;}    int l=-150,r=150,ans;    while(l<=r)    {        int mid=(l+r)/2;        if(chk(mid))        {            ans=cnt-nd*mid;            l=mid+1;        }        else r=mid-1;    }    printf("%d\n",ans);    return 0;}
原创粉丝点击