【模板】最小度限制生成树 题解

其他的题解感觉都好高级,分享一种好想且好实现的方法。

我们可以先把点 s 和与其相连的边都删除,我们发现剩下的部分变成了一些连通块。

我们不难发现,当要求与 s 点相连的边的个数为 k 时,我们的连通块个数显然是 k 的。

接下来这个问题就转化成了:n1 个点中生成一个 k 个连通块,要求边权和 + 每个块中到最小的 点到 s 的边权 最小(注意断句),其实这就相当于我们生成了最小度限制生成树。

接下来的部分,我们假设 s 和除 s 外的每个点都有边。

我们不难发现 k=n1 是简单的,答案就是每个点和 s 的最小边的和。

我们接下来考虑合并两个连通块(单个点也是联通块),将他们变成一个连通块,我们发现将 u 连通块并入 v 的代价是 min(lenuv)max(min(lensu),min(lensv))

我们令 fumin(lenuv)gumin(lensu)

fumax(gu,gv) 可能很难维护,但是,如果我们换一个角度,我们认为把 u 连通块并入 v 的代价记作 fugu。当我们统计 v 并入 u 的时候,另外一种情况也会被统计。

接下来问题就转化成了我们要全局维护一个 fugu 的最小值。

我们对于每一个连通块使用一个堆来维护 fu,合并连通块就直接并查集合并,同时统计 gu,堆的合并使用启发式合并,然后全局使用一个堆来维护 fugu,每次合并后我们取出堆顶合并两点后再对全局的堆进行更新即可。

请注意:

更新的时候我们要注意:如果堆首所对应的 u,v 已经在同一连通块内,我们要一直弹出直到堆首两个点不在同一连通块内。

复杂度:O(m×log2 m)

当然,我们也可以使用可并堆,复杂度就可以变成 O(m×log m)

如果每一次我们都在可并堆合并的时候启发式的删除所有两个连通块相连的边,复杂度就变成了O(m×log n)(但是我懒了,直接用 pbds,就没法做这个操作)。

我们接下来解决不够的边,如果一开始 s 与一个点没有边相连,我们就连一条极大的边,最后特判一下就行了。

int n, m, num[L], fa[L], _s, k, ww;

ll ans;

int _u[L], _v[L];

__gnu_pbds::priority_queue<pii, greater<pii>, __gnu_pbds::pairing_heap_tag> s[L], S;

int getfa(int w) {
    if(fa[w] == w) return fa[w];
    else return fa[w] = getfa(fa[w]);
}

void merge(int a, int b) {
    S.pop();
    int af = getfa(a), bf = getfa(b);
    if(s[af].size() > s[bf].size()) swap(af, bf);
    fa[af] = bf;
    num[bf] = min(num[bf], num[af]);
    s[bf].join(s[af]);
    while(!s[bf].empty() && getfa(_u[s[bf].top().second]) == getfa(_v[s[bf].top().second])) s[bf].pop();
    if(!s[bf].empty()) S.push({s[bf].top().first - num[bf], s[bf].top().second});
}

signed main() {
    n = read(), m = read(), _s = read(), k = read();
    if(k == 0) return puts("Impossible"), 0;
    rep(i, 1, n) num[i] = P, fa[i] = i;
    rep(i, 1, m) {
        int u = read(), v = read(), w = read();
        if(u == v) continue;
        if(u != _s && v != _s) s[u].push({w, i}), s[v].push({w, i});
        else num[v] = min(num[v], w), num[u] = min(num[u], w);
        _u[i] = u, _v[i] = v;
    }
    rep(i, 1, n) if(i != _s) ans += num[i];
    rep(i, 1, n) if(i != _s) if(num[i] != P) ww ++;
    if(n - 1 == k) {
        if(k > ww || S.empty()) puts("Impossible"); else cout << ans << endl;
        return 0;
    }
    rep(i, 1, n) if(i != _s) if(s[i].size()) {S.push({s[i].top().first - num[i], s[i].top().second});}
    per(_, n - 2, 1) {
        while(!S.empty() && getfa(_u[S.top().second]) == getfa(_v[S.top().second])) S.pop();
        if(!S.empty()) ans += S.top().first; 
        if(_ == k) {
            if(k > ww || S.empty()) puts("Impossible"); else cout << ans << endl;
            return 0;
        }
        merge(_u[S.top().second], _v[S.top().second]);
    } 
    return 0;
}
posted @   KafuChino  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示