NC53074 Forsaken喜欢独一无二的树

题目链接

题目

题目描述

​ 众所周知,最小生成树是指使图中所有节点连通且边权和最小时的边权子集。

​ 不过最小生成树太简单了,我们现在来思考一个稍微复杂一点的问题。

​ 现在给定一个 \(n\) 个点, \(m\) 条边的图,每条边 \(e_i\) 都有一个权值 \(w_i\) 。定义删除一条边 \(e_i\) 的代价为 \(w_i\) ,并且你可以对这个图执行任意次删边操作。

​ 设这个图的最小生成树权值和为 \(sum\) ,定义一个图的最小生成树是独一无二的当且仅当这个图的边集中没有除最小生成树外的其他子集能满足权值和为 \(sum\) 且使得所有点连通。一个图刚开始可能没有独一无二的最小生成树,现在你可以删除一些边,使得剩下的边的最小生成树大小依然为 \(sum\) 并且这个图的最小生成树是独一无二的。

​ 现在我们想要知道删除的边的权值和最小是多少?

输入描述

第一行输入为 \(n\)\(m\) ,表示这个图的点数和边数。
接下来 \(m\) 行,每行三个值 \(u_i\) , \(v_i\) , \(w_i\) ,分别代表每条边的两个端点和边权。

输出描述

一个整数,代表删除的边的最小权值和。

示例1

输入

1 0

输出

0

备注

\(1 \leq n\leq 2e5\)
\(n - 1 \leq m\leq 2e5\)
\(1 \leq u_i, v_i \leq n\)
\(1 \leq w_i \leq1e9\)

题解

知识点:枚举,双指针,最小生成树。

最小生成树出现不同解,是因为同一权值的边连接两个已经连通的点。因此我们可以每次把相同权值的边放一起考虑,如果某条边所连两点已经连通,这个条边就是可以删掉的。

时间复杂度 \(O(m\log m)\)

空间复杂度 \(O(m)\)

代码

#include <bits/stdc++.h>
#define ll long long

using namespace std;

const int N = 2e5 + 7, M = 2e5 + 7;
struct edge {
    int u, v, w;
}e[M];

int fa[N];
int find(int x) {
    return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void merge(int x, int y) {
    fa[find(x)] = find(y);
}


int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, m;
    cin >> n >> m;
    for (int i = 1;i <= n;i++) fa[i] = i;
    for (int i = 1;i <= m;i++) {
        int u, v, w;
        cin >> u >> v >> w;
        e[i] = { u,v,w };
    }

    sort(e + 1, e + m + 1, [&](edge a, edge b) {return a.w < b.w;});
    int l = 1, r = 1;
    ll ans = 0;
    while (l <= m) {
        while (r <= m) {
            if (e[r].w != e[l].w) break;
            r++;
        }
        for (int i = l;i < r;i++)
            if (find(e[i].u) != find(e[i].v)) ans += e[i].w;
        for (int i = l;i < r;i++)
            if (find(e[i].u) != find(e[i].v)) ans -= e[i].w, merge(e[i].u, e[i].v);
        l = r;
    }
    cout << ans << '\n';
    return 0;
}
posted @ 2023-01-03 13:08  空白菌  阅读(23)  评论(0编辑  收藏  举报