CF2081D - MST in Modulo Graph
听说是一类题型,所以且做记录。
引用官方题解:
注意到,有用边的数量不超过 \(n\log{n}\) 条
我哪来的注意力。
通过枚举不同 \(p_i\) 的权值 \(k\),使得其他数值在 \((kp_i, \, (k+1)p_i)\) 的范围内,容易发现,满足 \(kx \le y < z < (k+1)x\) 的点,连接 \((x, \, y), \, (y, \, z)\) 显然比 \((x, \, y), \, (z, \, x)\) 更优秀,所以通过这样枚举后二分出第一个满足条件的 \(y\),复杂度是调和级数,单调栈可以做到 \(O(n\log{n})\),处理边(基数排序可以做到 \(O(n\log{n})\))后 Kruskal 算法上一波就行了。
复杂度 \(O(n\log^2{n})\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> PII;
const int N = 5e5 + 10;
int p[N];
int find(int x) {
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
void solve() {
int n;
cin >> n;
vector<int> a;
a.push_back(0);
for (int i = 0; i < n; i ++ ) {
int t;
cin >> t;
a.push_back(t);
}
sort(a.begin(), a.end());
a.erase(unique(a.begin(), a.end()), a.end());
n = a.size() - 1;
vector<PII> edge, w;
int maxn = *max_element(a.begin(), a.end());
for (int i = 1; i < n; i ++ ) {
for (int k = 1; k * a[i] <= maxn; k ++ ) {
int t = lower_bound(a.begin() + i + 1, a.end(), k * a[i]) - a.begin();
if (a[t] >= (k + 1) * a[i] || a[t] < k * a[i]) continue;
// cout << a[t] << " <-> " << a[i] << " k = " << k << " " << a[t] - k * a[i] << "\n";
w.push_back({a[t] - k * a[i], edge.size()});
edge.push_back({i, t});
}
}
for (int i = 1; i <= n; i ++ ) p[i] = i;
sort(w.begin(), w.end());
int ans = 0;
for (auto items : w) {
int x = edge[items.second].first, y = edge[items.second].second;
x = find(x), y = find(y);
if (x != y) {
p[x] = y;
ans += items.first;
}
}
cout << ans << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while (T -- ) solve();
return 0;
}