MST in Modulo Graph
思维怎么练?
题意
给定一个包含 \(n\) 个顶点的完全图, 其中第 \(i\) 个顶点的权重为 \(p_i\). 连接顶点 \(x\) 和顶点 \(y\) 的边的权重等于 \(\operatorname{max}(p_x, p_y) \bmod \operatorname{min}(p_x, p_y)\).
请找到一组 \(n - 1\) 条边, 使得这组边能够连接所有 \(n\) 个顶点, 并且这组边的总权重最小.
思路
由于边数是 \(\mathcal{O}(n^2)\) 级别的, 我们不能直接进行连边跑 Kruskal. 不难发现, 其实在图中很多边是不必要或者一定不优的, 我们考虑从减少连边的角度入手.
首先, 对于权值相同的节点, 我们可以将它们全部缩成一个点 \((\)因为两两连边代价是 0, 不会产生贡献\()\) 再来考虑. 假设当前枚举的点权值为 \(x\), 那么与它相连的边的权值一定在 \(0 \sim x - 1\) 的范围内, 我们考虑将其他点分成几个区间: \([x, 2x], [2x, 3x], \dots, [(k - 1)x, kx]\), 其中 \(\displaystyle k = \lfloor \frac{\max p_i}{x} \rfloor\). 对于每一个区间, 我们只向点权最小的那一个点连边. 证明一下, 如果存在 \(x < y < z < 2x\), 其中 \(y, z\) 均指点权, 那么 \(x \to y, y \to z\) 一定比 \(x \to y, x \to z\) 优, 至于 \(y \to z\) 的边, 我们会在枚举 \(y\) 时操作.
这样, 边的数量减少到的 \(\mathcal{O}(n \log n)\) 级别. 再跑一个 Kruskal, 总复杂度 \(\mathcal{O}(n \log^2 n)\).
void init() {e.clear();read(n);for (int i = 1; i <= n; ++i) {read(p[i]);}sort(p + 1, p + n + 1);n = unique(p + 1, p + n + 1) - p - 1;iota(fa, fa + n + 1, 0);
}void calculate() {for (int i = 1; i <= n; ++i) {for (int _ = 1, lim = p[n] / p[i]; _ <= lim; ++_) {int k = _ * p[i], pos = lower_bound(p + 1, p + n + 1, k) - p;if (pos == i) {pos = upper_bound(p + 1, p + n + 1, k) - p;}if (pos > n) {continue;}e.push_back({i, pos, p[pos] % p[i]});}}ranges::sort(e, [](Edge x, Edge y) {return x.w < y.w;});int cnt = 0;ll ans = 0;for (auto [u, v, w] : e) {if (cnt == n - 1) {break;}u = find(u), v = find(v);if (u ^ v) {++cnt;ans += w;fa[u] = v;}}printf("%lld\n", ans);
}