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

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

嗯,就是这样

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值