年轻人的第一道国家集训队题目


如果我们不做任何处理,直接跑MST(Minimum Spanning Tree,最小生成树),结果会有三种:

  • 正好跑出 条白边

  • 白边多了

  • 白边少了

第一种情况自然是最好的

剩下两种情况如何解决?


引起白边少的原因:黑边的边权相对较小,程序贪心地选择了更多的黑边

引起白边多的原因:白边的边权相对较小,程序贪心地选择了更多的白边

那么如果我们给白边相应地减去/加上一些边权,不就可以达成目标了?


考虑二分答案。

我们二分一个 表示我们当前要给白边加上 来达成目标

边界分别是边权最小值(-100)和边权最大值(100)

由于题面保证有答案,所以直接输出
即可,其中 为(加上边权后)最小生成树的权值和

Check(mid) 怎么写?


我们将所有白边的边权加上(即),跑一遍最小生成树,判断一下拿到的白色边数量是否大于等于要求的数量,如果是就更新一下左边界并记当前的,否则就更新一下右边界

注意不要忘了把边权减回来

(不要在意 是什么意思)


刚才我们不是记录了一下吗,这个就相当于是一个正确的、能选出正好 条白边的 值,再将所有白边的边权都加上这个 ,跑一遍最小生成树即可

答案不要忘了减去加上的边权(也就是

那么最后的答案就是

代码实现

#include 
#include 
#include 

#define FILE_IN(__fname) freopen(__fname, "r", stdin)
#define FILE_OUT(__fname) freopen(__fname, "w", stdout)
#define IMPROVE_IO() std::ios::sync_with_stdio(false)

#define WHITE 0
#define BLACK 1

using std::cin;
using std::cout;
using std::endl;
using std::max;

const int MAXV = 50000 + 10;
const int MAXE = 100000 + 10;
const int MAXW = 100;

struct Edge {
    int prev, next, weight, add;
    bool color;
    // 1 -> black, 0 -> white

    Edge() { prev = next = weight = color = add = 0; }

    bool operator < (const Edge &that) const {
        if (weight == that.weight) return color < that.color;
        return weight < that.weight;
    }
} edge[MAXE << 1];

int V, E, Need, cnt, ans;

int U[MAXV << 1];

int Find(int x) {
    if (U[x] == x) return U[x];
    return U[x] = Find(U[x]);
}

bool Union(int x, int y) {
    x = Find(x), y = Find(y);
    if (x == y) return false;
    U[x] = y;
    return true;
}

int Kruskal() {
    int whiteEdge = 0;
    for (int i = 1; i <= V; ++i) U[i] = i;
    std::sort(edge + 1, edge + 1 + E);
    int tot = 0;
    ans = 0;
    for (int i = 1; i <= E; ++i) {
        if (Union(edge[i].prev, edge[i].next)) 
            ans += edge[i].weight, ++tot, whiteEdge += (edge[i].color == WHITE);
        if (tot == V - 1) break;
    }
    return whiteEdge;
}

bool Check(int add) {
    for (int i = 1; i <= E; ++i) {
        if (edge[i].color == WHITE) edge[i].weight += add;
    }
    bool Ans = (Kruskal() >= Need);
    for (int i = 1; i <= E; ++i) {
        if (edge[i].color == WHITE) edge[i].weight -= add;
    }
    return Ans;
}

int main() {
    IMPROVE_IO();
    cin >> V >> E >> Need;
    for (int i = 1; i <= E; ++i) {
        cin >> edge[i].prev >> edge[i].next >> edge[i].weight >> edge[i].color;
        ++edge[i].prev;
        ++edge[i].next;
    }
    int l = -MAXW, r = MAXW;
    int Run = 0;
    while (l <= r) {
        int mid = ((l + r) >> 1);
        if (Check(mid)) {
            l = mid + 1;
            Run = mid;
        } else {
            r = mid - 1;
        }
    }
    Check(Run);
    cout << ans - Need * Run << endl;
    return 0;
}