题意
给一张 \(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;
}
标签:arr,int,题解,询问,MST,多校,sqrt,fa,maxn
From: https://www.cnblogs.com/TJUHuangTao/p/18310814