Luogu P14080 [GESP202509 八级] 最小生成树 题解 [ 蓝 ] [ 最小生成树 ] [ 并查集 ] [ 树链剖分 ] [ 次小生成树 ] [ 倍增 ]

最小生成树:板、典、原。

不难想到我们可以先找出原图的 MST,然后对于不在 MST 上的边,删掉它也不会影响答案。因此我们只需要考虑在 MST 上的边删掉有什么影响即可。

假设当前被删掉的边为 \(e\),两个端点为 \(u, v\),那么如果要使得 MST 继续保持连通,则添加的新边 \((a, b)\) 需要满足 \(e\)原 MST 里 \(\bm{a \to b}\) 的路径上。在此基础上,找到这些边的边权最小值,就可以求出新的 MST 了。

考虑如何找到满足要求的边权最小值,显然我们可以让非 MST 边去反过来贡献树边,给 \(a\to b\) 路径上的每一条边取一个最小值。最后删除边 \(e\) 的时候只需查询 \(e\) 上被覆盖的边权最小值即可。显然可以用树剖实现,时间复杂度 \(O(n\log^2 n)\)

但是这是 GESP,不能考树剖,于是我们换一种简单的思路来实现。注意到这是个树链覆盖的操作,且最后覆盖的值由最小值决定。所以可以将边按权值排序之后,从小到大依次覆盖未被覆盖过的 MST 树边,这个显然可以用并查集进行维护,具体维护的是每个节点未被覆盖的深度最深的祖先(包括自己)。因为均摊下来每条边最多只会覆盖一次,所以最后的时间复杂度为 \(O(n\log n)\)。瓶颈在于排序。

#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi = pair<int, int>;
const int N = 100005, M = 400005;
const ll inf = 0x3f3f3f3f3f3f3f3f;
int n, m;
ll res = 0, ans[M], ysans[M];
vector<pi> g[N];
bool inmst[M];
struct Edge{
    int u, v, id;
    ll w;
    bool operator < (const Edge & t) const{
        return (w < t.w);
    }
}e[M];
struct DSU{
    int fa[N];
    void init()
    {
        for(int i = 1; i <= n; i++) fa[i] = i;
    }
    int findf(int x)
    {
        if(fa[x] != x) fa[x] = findf(fa[x]);
        return fa[x];
    }
    void combine(int x, int y)
    {
        int fx = findf(x), fy = findf(y);
        fa[fx] = fy;
    }
}dsu1, dsu2;
int dep[N], bleg[N], fax[N];
void dfs(int u, int fa)
{
    dep[u] = dep[fa] + 1;
    fax[u] = fa;
    for(auto eg : g[u])
    {
        int v = eg.fi, eid = eg.se;
        if(v == fa) continue;
        dfs(v, u);
        bleg[v] = eid;
    }
}
void update(int u, int v, ll w)
{
    u = dsu2.findf(u), v = dsu2.findf(v);
    while(u != v)
    {
        if(dep[u] < dep[v]) swap(u, v);
        ans[bleg[u]] = w;
        dsu2.combine(u, fax[u]);
        u = dsu2.findf(u);
    }
}
int main()
{
    //freopen("sample.in", "r", stdin);
    //freopen("sample.out", "w", stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> m;
    for(int i = 1; i <= m; i++)
    {
        int u, v, w;
        cin >> u >> v >> w;
        e[i] = {u, v, i, w};
    }
    sort(e + 1, e + m + 1);
    memset(ans, -0x3f, sizeof(ans));
    dsu1.init();
    dsu2.init();
    for(int i = 1; i <= m; i++)
    {
        int u = e[i].u, v = e[i].v, w = e[i].w;
        if(dsu1.findf(u) != dsu1.findf(v))
        {
            dsu1.combine(u, v);
            res += w;
            g[u].push_back({v, i});
            g[v].push_back({u, i});
            inmst[i] = 1;
        }
    }
    dfs(1, 0);
    for(int i = 1; i <= m; i++)
    {
        int u = e[i].u, v = e[i].v, w = e[i].w;
        if(!inmst[i]) update(u, v, w);
    }
    for(int i = 1; i <= m; i++)
    {
        int u = e[i].u, v = e[i].v, w = e[i].w, qid = e[i].id;
        if(!inmst[i]) ysans[qid] = res;
        else ysans[qid] = res - w + ans[i];
    }
    for(int i = 1; i <= m; i++) cout << (ysans[i] < 0 ? -1 : ysans[i]) << "\n";
    return 0;
}

另一种做法好像是类似次小生成树,用倍增去做,但是我不会。

posted @ 2025-09-30 17:38  KS_Fszha  阅读(68)  评论(0)    收藏  举报