2024牛客暑期多校训练营2 B.MST(题解)

题意

给一张 \(n\) 个点, \(m\) 条边的无向图,\(q\) 次询问,每次询问给 \(k\) 个结点,问这 \(k\) 个结点的诱导子图(也就是原图中抽出这些结点,以及原图中这些节点之间有的边) 的最小生成树是多少,不连通输出-1, 保证 \(q\) 次询问加起来问到的点的数量 \(\sum k_i \leq 10^5\)

思路

就1秒时间,这个问题看着很困难,很容易想到要去根号分治,但是队友说,无论怎么分,给你 \(\sqrt n\) 次询问,每次询问 \(\sqrt n\) 个点的完全图,总的边数都是 \(n \sqrt n\) 级别,再用最小生成树的算法,铁t。

然后我赛时的想法是,既然询问到的总边数肯定是 \(n \sqrt n\)级别,那肯定得让最小生成树那边求的快一点,不可能每次都排序用克鲁斯卡尔。然后思路变成,总的对边排一次序,然后遍历每条边,判度这些边需要添加到哪些询问里。然后因此对询问的大小进行根号分治,询问大的,直接用数组存起来它们都问了哪些点。小的,枚举顶点 \(n^2\) 预处理,可能涉及到的边。然后离线同时对这多个询问的并查集去维护。最后还是t了。

赛后看群里,发现大家就是直接在线跑 \(q\) 次克鲁斯卡尔 \(n \sqrt n logn\) 冲过去了,然后每次是对加入的边进行根号分治。如果询问的顶点大于 \(\sqrt n\) 个,直接遍历已经排序过的总的边,判断每个边的两个顶点是否都在这个询问内,在的话加入并查集维护。因为这样的询问不超过 \(\frac{n}{\sqrt n} = \sqrt n\) 个,所以复杂度是 \(\sqrt n \times m\),然后询问的点比较少,那么直接 \(O(n^2)\) 的遍历两个顶点,看看map存的邻接矩阵里面有没有这条边,有的话把这个边加入并查集维护。复杂度是 \(O((\sqrt n) ^ 2 logn)\)。然后块长就设个 \(\sqrt n\) 附近,就能直接冲过去了。(看排行榜前面杭电的代码,块长设了一个奇妙的 \(\frac{m}{log(m)}\)),时间会快很多,不知道是怎么算的。

代码如下:

#include <bits/stdc++.h>
#define ll long long
#define pii pair<int, int>
#define all(a) a.begin(), a.end()
using namespace std;
const int maxn = 1e5 + 10;
const int mod = 998244353;
struct edge {
    int u, v, w;
} e[maxn];
bool operator<(edge a, edge b) {
    return a.w < b.w;
}
map<pii, int> mp;
int fa[maxn], arr[maxn];
int us[maxn];
int Find(int x) {
    return fa[x] == x ? fa[x] : fa[x] = Find(fa[x]);
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    // freopen("./B-MST/6.in", "r", stdin);
    // freopen("1.out", "w", stdout);
    int n, m, q;
    cin >> n >> m >> q;
    int sz = sqrt(m / log2(m));
    for (int i = 1; i <= m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        e[i] = edge{u, v, w};
        mp[pii(u, v)] = mp[pii(v, u)] = w;
    }
    sort(e + 1, e + 1 + m);
    while (q--) {
        int k;
        cin >> k;
        for (int i = 1; i <= k; i++)
            cin >> arr[i], fa[arr[i]] = arr[i];
        if (k > sz) {
            vector<edge> tem;
            for (int i = 1; i <= m; i++) {
                auto [u, v, w] = e[i];
                if (fa[u] && fa[v])
                    tem.push_back(edge{u, v, w});
            }
            sort(all(tem));
            int cnt = k;
            ll ans = 0;
            for (auto [u, v, w] : tem) {
                u = Find(u), v = Find(v);
                if (u != v) {
                    fa[u] = v;
                    ans += w;
                    cnt--;
                }
            }
            if (cnt == 1)
                cout << ans << "\n";
            else
                cout << -1 << "\n";
        } else {
            vector<edge> tem;
            for (int i = 1; i <= k; i++)
                for (int j = i + 1; j <= k; j++)
                    if (mp.count(pii(arr[i], arr[j])))
                        tem.push_back(
                            edge{arr[i], arr[j], mp[pii(arr[i], arr[j])]});
            sort(all(tem));
            int cnt = k;
            ll ans = 0;
            for (auto [u, v, w] : tem) {
                u = Find(u), v = Find(v);
                if (u == v)
                    continue;
                fa[u] = v;
                ans += w;
                cnt--;
            }
            if (cnt == 1)
                cout << ans << "\n";
            else
                cout << -1 << "\n";
        }
        for (int i = 1; i <= k; i++)
            fa[arr[i]] = 0;
    }
    return 0;
}
posted @ 2024-07-19 09:35  TJUHuangTao  阅读(208)  评论(5)    收藏  举报