题解:P2619 [国家集训队] Tree I

· · 题解

题目链接

思路

看到好多题解都提到了二分,但似乎没有严谨地证明 wqs 二分中函数的凸性,在这里介绍另外一种理解方法。

定义 G(c) 表示每条白边边权加了 c 之后的图,w(c,T) 表示在 G(c)T 这棵生成树上的边权和。

有了引理 3,二分变得可行。之后的内容较为显然,在此不再赘述。

代码

#include <bits/stdc++.h>
using namespace std;
const int M = 1e5 + 100;
const int N = 5e4 + 100;
struct E{
    int u, v, w, c;
    bool operator < (E ot)const{
        return w != ot.w ? w < ot.w : c < ot.c;
    }
}e[M];
int n, m, ned, ans;
int fa[N];
int find(int x){return x == fa[x] ? x : (fa[x] = find(fa[x]));}
int sum, cnt, wt;
void kruskal(){
    for(int i = 1;i <= n + 1;i++) fa[i] = i;
    sum = cnt = wt = 0;
    sort(e + 1, e + 1 + m);
    for(int i = 1;i <= m;i++){
        int u = find(e[i].u), v = find(e[i].v);
        if(u == v) continue;
        fa[u] = v;
        sum += e[i].w;
        if(!e[i].c) wt++;
        if(++cnt == n - 1) break;
    }
}
int main(){
    cin >> n >> m >> ned;
    for(int i = 1;i <= m;i++){
        int u, v, w, c;
        cin >> u >> v >> w >> c;
        e[i] = {u + 1, v + 1, w, c};
    }
    int l = -101, r = 101;
    while(l <= r){
        int mid = (l + r) >> 1;
        for(int i = 1;i <= m;i++) if(!e[i].c) e[i].w += mid;
        kruskal();
        if(wt >= ned) l = mid + 1, ans = sum - ned * mid;
        else r = mid - 1;
        for(int i = 1;i <= m;i++) if(!e[i].c) e[i].w -= mid;
    } 
    cout << ans << endl;
    return 0;
}