BZOJ 2654 tree 详解(最小生成树 kruskal 二分)

来源:互联网 发布:网络数据论文 编辑:程序博客网 时间:2024/06/05 09:52

BZOJ 2654 tree

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

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

Output
  一行表示所求生成树的边权和。

Sample Input
2 2 1
0 1 1 1
0 1 2 0

Sample Output
2

HINT
数据规模和约定

0:V<=10

1,2,3:V<=15

0,..,19:V<=50000,E<=100000

所有数据边权为[1,100]中的正整数。

思路:
巧妙,
我们发现,如果考虑权值,那么就没有办法处理黑白,如果考虑黑白,那么又做不到权值的最优。
所以我们冥思苦想如何把黑白区分开,又不能改变权值之间的优劣。于是就有了一个神奇的解法,如果我们给白边增加权值,那么求最小生成树时,由于白边权值增大,所有不容易选白边。
这样一来,我们既没有改变他们权值的顺序,而且白边选的就会少一点,最小生成树有保证了当前状态下的最优解。
记f(x)为给白边增加x权值,求出最小生成树后,白边的数量,又可以发现,f(x)随x增大而减小,所以二分x的值。
可能有人会疑惑,有可能x在[a,b]中都满足白边数目为need,这个时候x与ans(树的大小)之间并没有单调关系,怎么能二分呢?针对这道题,只要我们找到了满足白边数目为need的状态,那么它就是答案。为什么?因为我们的ans只与选了那些边有关,x我们最终是要剪掉的,又因为保证了need条白边,所以剩下的选择都是黑边了,而黑边是不受x影响的,所以只要满足了need,那么就是答案。

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#define lim 1000#define ll long long using namespace std;const int N = 100005;int n, m, cnt, tot, goal, ans;int u[N], v[N], w[N], c[N], fa[N];struct edge{    int u, v, w, c;}ed[N<<1];bool cmp(edge a, edge b){    if(a.w == b.w) return a.c < b.c;//优先白     else return a.w < b.w;}int find(int x){    if(x == fa[x]) return x;    else return fa[x] = find(fa[x]);}bool check(int x){    tot = cnt = 0;    for(int i=1; i<=n; i++) fa[i] = i;    for(int i=1; i<=m; i++){        ed[i].u = u[i];        ed[i].v = v[i];        ed[i].w = w[i];        ed[i].c = c[i];        if( !c[i] ) ed[i].w += x;    }    sort(ed+1, ed+m+1, cmp);    for(int i=1; i<=m; i++){//kruskal        int p = find(ed[i].u), q = find(ed[i].v);        if(p != q){            fa[p] = q;            tot += ed[i].w;            if( !ed[i].c ) cnt++;        }    }    return cnt >= goal;}int main(){    scanf("%d%d%d", &n, &m, &goal);    for(int i=1; i<=m; i++){        scanf("%d%d%d%d", &u[i], &v[i], &w[i], &c[i]);        u[i]++; v[i]++;//更改编号为1开始,方便check     }    int l=-lim, r=lim;    while(l <= r){//二分         int mid = (l + r) >> 1;        if( check(mid) ){            l = mid + 1;            ans = tot - goal * mid;//还原         }        else r = mid - 1;    }    printf("%d", ans);    return 0;}
原创粉丝点击