【BZOJ2654】Tree

题意

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

分析

第一想法是贪心,但是只考虑到贪心会让生成树可能不连通,于是开心地求了割边,把割边的顶点combine后再加白边,再加黑边。

WA到只有10分。因为本质还是在贪心,只要贪心地把黑白边分开就不对

比如只有三个顶点的一张图,有四条边,中间的一个顶点到另外两个顶点分别有一条白边一条黑边,此时限制只能加入一条白边。

假如到左边点的白边权值为1,黑边权值为3,到右边点的白边权值为2,黑边权值为10.

贪心,用左边的白边,那么不得不用右边的黑边,则sum=1+10=11>3+2=5.很明显贪心错误

虽然先求割边再分黑白跑Kruskal这是错误做法,但也有一定启示。在真正求生成树的时候,似乎已经有一些边被加进来了,导致已经产生了一定值

那么我们既然无法区分开黑白边,我们干脆忽略黑边过多的影响,仅使它们的权值对答案造成影响。

即对于每条白边,加上一个mid值(正负皆可)来控制数量(改变白边的排列顺序),暂且可理解为因为有黑边的加入所造成的答案偏移值。而边权范围为-100~100,很明显,二分这个偏移值

对于每次二分出来的答案,需要通过kruskal检验,如果用到的白边超过了限制,则需要加入更大的偏移值。

对于边权相同的黑白边,要把颜色相同的放在一起,否则影响正确性(影响白边数量单调性)

二分结束后的l不是答案!!需要跑一边kruskal带进去求出真正的sum,最后输出的答案要减去k条白边最后一次操作的偏移值

#include<cstdio>
#include<algorithm>
#include<queue>
#include<cmath>
#include<iostream>
using namespace std;
#define N 505000
int n,m,k,ans,sum;
int fa[N],first[N],ui[N],vi[N],wi[N],ci[N];
struct email
{
    int u,v,w,c;
}e[N*10];
inline void add(int u,int v,int w,int c,int p)
{
    e[p].u=u;e[p].v=v;e[p].w=w;e[p].c=c;
}
bool cmp(email a,email b)
{
    if(a.w!=b.w)
        return a.w<b.w;
    return a.c<b.c;
}

inline int find(int x)
{    return fa[x]==x?x:fa[x]=find(fa[x]);}
            
bool check(int mid)
{
    int ans=0,num=0;    
    for(int i=0;i<n;i++)
        fa[i]=i;
    for(int i=1;i<=m;i++)
    {
        add(ui[i],vi[i],wi[i],ci[i],i);
        if(!ci[i])e[i].w+=mid;
    }
    sort(e+1,e+1+m,cmp);
    sum=0;
    for(int i=1;i<=m;i++)
    {
        int u=e[i].u,v=e[i].v,w=e[i].w,c=e[i].c;
        int fax=find(u),fay=find(v);
        if(fax!=fay)
        {
            fa[fax]=fay;
            num++;sum+=w;
            if(!c)ans++;
        }    
        if(num==n-1)
            break;
    }
    if(ans>=k)
        return true;
    return false;
}

int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=m;i++)
        scanf("%d%d%d%d",&ui[i],&vi[i],&wi[i],&ci[i]);
    int mid,l=-100,r=100;
    while(l<r)
    {
        mid=l+r+1>>1;
        if(check(mid))
            l=mid;
        else
            r=mid-1;
    }
    check(l);
    printf("%d\n",sum-k*l);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/NSD-email0820/p/9440463.html