CF1108F 题解

题目传送门

题目大意:

给定一个有 \(n\) 个点,\(m\) 条边的无向连通图,每次操作是将某条边的权值加一,求最少操作次数使得该无向连通图的最小生成树唯一。

修改操作不能改变 MST 的权值和。

思路:

修改操作不能改变 MST 的权值和。 这句话很关键,它告诉我们只能在最开始的最小生成树上考虑,否则情况就很冗杂了。

所以我们肯定得先求出原图的最小生成树。

不难想到当最小生成树不唯一时,会有其他非树边去替换最小生成树上的边,那么什么时候可以替换呢?

假设我们已经求出了原图的一颗最小生成树,并且扫描到一条非树边 $ e = (a, b, w)$,那么它必定和最小生成树上 \(a,b\) 之间的路径构成一个环,在这个环上,断掉任意一条边,它一定还是一棵生成树。

设这条环上权值最大的树边为 \(e'\),如果在这条环上的树边的最大权值等于了这条非树边,即 \(e'_w = e_w\),那么断掉 \(e\) 或断掉 \(e'\) 都是可行方案了,即这时候 \(e\) 可以替换 \(e'\),这就会使最小生成树不唯一。

为了防止这种情况发生,我们只需要将 \(e_w + 1\) 即可,就算 \(e'\) 可能会有很多条,但因为 \(e_w\) 已经大于环上所有的边权了,所以无法替换。

求树上两点之间的最大边权可以用倍增 LCA 解决。

综上所述,我们先用 \(\texttt{kruskal}\) 算法求出最小生成树,顺便将最小生成树的边存下来,再 bfs 预处理一下最小生成树的 LCA,然后依次扫描每条非树边,若满足上述性质,则答案加一。

时间复杂度为 \(O(n\log n)\)。(假设 \(n,m\) 同级)

\(\texttt{Talk is cheap, show you the code.}\)

#include <queue>
#include <cmath>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 200010;
int n, m;
int h[N], e[N << 1], ne[N << 1], w[N << 1], idx;
struct node{
    int a, b, w;
    bool type;
    bool operator< (const node &o) const {
        return w < o.w;
    }
}edges[N];
int p[N];
int dep[N], f[N][22], T;
int maxd[N][22]; //maxd[i][j] 表示节点 i 到它的 2^j 辈祖先这条路径上的最大边权

void add(int a, int b, int c) {
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

int find(int x) {
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}

void bfs(int s) {
    queue<int> q;
    q.push(s);
    dep[s] = 1;
    while(!q.empty()) {
        int t = q.front();
        q.pop();
        for(int i = h[t]; ~i; i = ne[i]) {
            int j = e[i];
            if(dep[j]) continue;
            dep[j] = dep[t] + 1;
            f[j][0] = t;
            maxd[j][0] = w[i];
            for(int k = 1; k <= T; k++) {
                f[j][k] = f[f[j][k - 1]][k - 1];
                maxd[j][k] = max(maxd[j][k - 1], maxd[f[j][k - 1]][k - 1]);
            }
            q.push(j);
        }
    }
}

int lca(int x, int y) {
    int res = -0x3f3f3f3f;
    if(dep[x] > dep[y]) swap(x, y);
    for(int i = T; i >= 0; i--) 
        if(dep[f[y][i]] >= dep[x]) {
            res = max(res, maxd[y][i]);
            y = f[y][i];
        }
    if(x == y) return res;
    for(int i = T; i >= 0; i--)
        if(f[x][i] != f[y][i]) {
            res = max(res, maxd[x][i]);
            res = max(res, maxd[y][i]);
            x = f[x][i], y = f[y][i];
        }
    res = max(res, maxd[x][0]);
    res = max(res, maxd[y][0]);
    return res;
}

int main() {
    memset(h, -1, sizeof h);
    scanf("%d%d", &n, &m);
    int a, b, w;
    for(int i = 1; i <= m; i++) {
        scanf("%d%d%d", &a, &b, &w);
        edges[i] = {a, b, w};
    }
    sort(edges + 1, edges + m + 1);
    int sum = 0;
    for(int i = 1; i <= n; i++) p[i] = i;
    int ori = 0; //随便找一个起点
    for(int i = 1; i <= m; i++) {
        a = edges[i].a, b = edges[i].b, w = edges[i].w;
        int x = find(a), y = find(b);
        if(x != y) {
            p[x] = y;
            add(a, b, w), add(b, a, w);
            edges[i].type = true; //树边标记一下
            ++sum;
            ori = a;
        }
        if(sum == n - 1) break;
    }
    T = (int)log2(n);
    bfs(ori);
    int res = 0;
    for(int i = 1; i <= m; i++)
        if(!edges[i].type) { //如果是非树边
            int tmp = lca(edges[i].a, edges[i].b);
            if(edges[i].w == tmp) ++res; //满足上述性质则累加答案
        }
    printf("%d", res);
    return 0;
}
posted @ 2024-07-25 08:08  Brilliant11001  阅读(7)  评论(0编辑  收藏  举报