P2619 [国家集训队2]Tree I

给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有 need条白色边的生成树。
题目保证有解。https://www.luogu.com.cn/problem/P2619

我们很容易知道如何求一个 最小权 的生成树
但是我们无法保证题目中所给的数据跑出的最小生成树恰好有 need 条边
奇妙的想法是,我们可以通过修改边的权值来修改跑出的最小生成树,我们让所有的白边都加上或者减去一个 权值 q
从而得到了各种最小生成树
需要注意的是

最小生成树不唯一

也就是说相同的总权值下,有不同的画法
不同的画法意味着可能造成不同的 need 值的选择

由于我们需要的是某种满足条件的选法
而不是某个最小的权值

所以我们在对于白边和黑边的选择顺序不应该是随意的。
我们让相同权值的情况下 在 Kruskal 的优先级中 白边大于黑边
我们在二分或者枚举q的过程中,如果保证有解的话,就一定存在一个q值,让最小生成树中的白边的个数恰好是need。
而我们在实际的选择中 可能 选择的优先级不是简单的 白边优先于 黑边
例如一个 14点是树,需要3条白边,而选择增加q权值的情况下,有13条白边被选,选q + 1的权值的情况下有 0 条白边被选
我们就可以知道,当need = 13的时候 如果不是单纯的白边优先黑边选择,我们只选择 3 条白边,

结论:
每一个Q值将会对应一个Ans
但由于最小生成树不唯一
每一个Ans可能对应着多种局面,即多种need
而这个need的范围是 :优先选白边的need 1
到优先选黑边的need 2
[need1,need2]

#include <bits/stdc++.h>
//本题的cost只有100,二分与枚举的时间复杂度差不多
using namespace std;
const int Mn = 2e5 + 5;
int fa[Mn];
struct node{
    int to,from,cost;
    int col;
}Edge[Mn];
int cnt = 0;
inline void Adde(int a,int b ,int c,int d){
    Edge[++cnt].from = a;
    Edge[cnt].to = b;
    Edge[cnt].cost = c;
    Edge[cnt].col = d;
}

bool cmp1(node a,node b){
    if(a.cost == b.cost) return a.col < b.col;
    return a.cost < b.cost;
}
bool cmp2(node a,node b){
    if(a.cost == b.cost) return a.col > b.col;
    return a.cost < b.cost;
}

int Find(int a){
    if(a == fa[a]) return a;
    return fa[a] = Find(fa[a]);
}
int n,m,need;
int t1,t2;
int Kruskal(int q){
    for(int i = 1;i <= m;i ++) if(!Edge[i].col) Edge[i].cost += q;
    t1 = t2 = 0;
    for(int i = 0;i < n;i ++) fa[i] = i;
    int c = 0;
    int now = 0;
    sort(Edge + 1,Edge + 1 + m,cmp1);
    int a,b;
    t1 = t2 = 0;
    while(c != n - 1){
        a = Find(Edge[++now].from);
        b = Find(Edge[now].to);
        if(a == b) continue;
        c ++;
        fa[a] = b;
        if(Edge[now].col == 0) t1 ++;
    }
    int ans = 0;
    c = 0;
    now = 0;
    sort(Edge + 1,Edge + 1 + m,cmp2);
    for(int i = 0;i < n;i ++) fa[i] = i;
    while(c != n - 1){
        a = Find(Edge[++now].from);
        b = Find(Edge[now].to);
        if(a == b) continue;
        c ++;
        fa[a] = b;
        if(Edge[now].col == 0)t2 ++;
        ans += Edge[now].cost;
    }
    for(int i = 1;i <= m;i ++) if(Edge[i].col == 0) Edge[i].cost -= q;
    return ans;
}
int main(){
    scanf("%d%d%d",&n,&m,&need);
    int a,b;
    int c,d;
    for(int i = 1;i <= m;i ++){
        scanf("%d%d%d%d",&a,&b,&c,&d);
        Adde(a,b,c,d);
    }
    int ans = 0;
    int l = -101,r = 101;
    int mid;
    while(l <= r){
        mid = (l + r) >> 1;
        int t = Kruskal(mid);
        if(t1 >= need && t2 <= need){
            ans = t - (need * mid);
            break;
        }else{
            if(t2 >= need){
                l = mid + 1;
            }else r = mid - 1;
        }
    }
    for(int i = -111;i <= 111;i ++){
        int t = Kruskal(i);
        if(t1 >= need && t2 <= need){
            ans = t - (need * i);
            break;
        }
    }
    printf("%d",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值