BZOJ 2654浅谈二分+最小生成树推导

来源:互联网 发布:java字符串拼接空格 编辑:程序博客网 时间:2024/06/11 04:20

这里写图片描述
世界真的很大
今天的风儿甚是喧嚣,豆大的雨滴悄咪咪地往下落
这也是一道挺有意思的题
难点在于推导,并不在于代码
主要是锻炼对模板的熟悉程度,和思维难度
但只是马马虎虎地靠感觉推导其实也并没有那么困难
只是认真想来有一点细思恐极的味道
还是看一下题为好
description

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

input

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

output

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

读完题就该想到最小生成树了
但关键是怎么把握白色边的个数
Kruscal算法的基本原理是边按权值排序,从小往大凑满n-1条边
如果我们能让白色边的排序稍微靠后,那取到白色边的可能就会降低,起码白色边的数量会小于等于之前的数量
让白色边的排序靠后的办法,无非也是给所有的白色边增加权值了
因为随增加权值的增加,白色边的数量单调递减,所以具有可二分的性质
于是乎我们采用二分的办法来逐渐逼近need值,算出那时的最小生成树的权值和
姑且看来就是这样了,代码也很好写
但还有点点
假设有一串增加的权值,使得白色边的数量都是need,那它们所对应的最小生成树的值是相等的吗?
现在我和大佬讨论后,有两种学说
1。是相等的。因为在增加的权值增加时,边排序里,白色边的相对位置肯定是向后挪的。如果使最小生成树的结构不同的话,必然有不属于之前的最小生成树的边加入,也就是说白色边增加权值后大于了新的边,使其相对位置提前。而由于白色边自己的相对位置是不变的,所以提前的只能是黑色边。这条新的黑色边不属于之前的最小生成树,即白色边向后挪的过程中超出了之前的最小生成树的n-1条边,那白色边的数量必然减少,而白色边的数量不变,所以矛盾,所以就是最小生成树的结构不变。
2。随着增加的权值的增加,白色边数量不变的情况下,最小生成树的权值是递增的。因为如果结构改变,使得一条白色边取不到的话,就必然有一条边来替代它的位置,白色边的相对位置不变,所以只能是黑色边。而白色边的数量不变,所以末尾必然有一条白色边补充。白色边的相对位置不变,所以后面补充的白色边的权值一定大于之前白色边的权值。所以是递增的、

这个嘛,两边都有道理,不管是哪边正确,只需要在二分的时候尽可能往左边靠就行了,保证取到need值的是增加的权值最少的点
完整代码:

#include<stdio.h>#include<algorithm>using namespace std;struct edge{    int u,v,w,clr;}ed[500010];int n,m,ned,sum=0,ans,fa[500010];bool cmp(const edge &a,const edge &b){    return a.w<b.w;}int getfather(int x){    if(x==fa[x]) return x;    return fa[x]=getfather(fa[x]);}int Kruscal(int x){    int bns=0,tot=0;sum=0;    for(int i=0;i<n;i++) fa[i]=i;    sort(ed+1,ed+m+1,cmp);    for(int i=1;i<=m;i++)    {        int x=getfather(ed[i].u);        int y=getfather(ed[i].v);        if(x!=y)        {            tot++;            fa[x]=y;            sum+=ed[i].w;            if(!ed[i].clr) bns++;        }        if(tot==n-1) break ;    }    sum-=x*bns;    for(int i=1;i<=m;i++)        if(!ed[i].clr) ed[i].w-=x;    return bns;}int check(int x){    for(int i=1;i<=m;i++)         if(!ed[i].clr) ed[i].w+=x;    return Kruscal(x);}int main(){    scanf("%d%d%d",&n,&m,&ned);    for(int i=1;i<=m;i++)        scanf("%d%d%d%d",&ed[i].u,&ed[i].v,&ed[i].w,&ed[i].clr);    int lf=-10000,rg=10000;    while(lf<=rg)    {        int mid=(lf+rg)>>1;        int tmp=check(mid);        if(tmp>=ned)        {            ans=sum;            lf=mid+1;        }        else rg=mid-1;    }    printf("%d",ans);    return 0;}

嗯,就是这样

原创粉丝点击