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

109 篇文章 4 订阅
17 篇文章 0 订阅

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值