loj#566. 「LibreOJ Round #10」yanQval 的生成树
\(\mu\) 取值即所选边权的中位数。
把每条边拆成两条(黑边和白边),边权分别为 \(\mu - w_i\) 和 \(w_i - \mu\),要求黑边和白边各选 \(\left\lfloor\dfrac{n-1}2\right\rfloor\) 条,求最大生成树。
可以直接 wqs 二分,时间复杂度 \(\mathcal O(nm \log w~\alpha(n))\)。
把所有边的边权同时加上一个 \(\mu\),则黑边边权为 \(2\mu - w_i\),白边边权为 \(w_i\),现在 \(\mu\) 的取值只会影响黑边的边权,wqs 二分的斜率 \(k\) 也只会影响黑边的边权,所以可以放到一起二分,时间复杂度 \(\mathcal O(m \log w~\alpha(n))\)。
一个值得注意的点是无论 \(n\) 的奇偶,取的黑边和白边的条数都是 \(\left\lfloor\dfrac{n-1}2\right\rfloor\)。
\(n\) 是奇数的时候显然,但 \(n\) 是偶数时,生成树上一定会有一条边无法确定颜色,否则会影响 wqs 二分中的答案,但本题下任一一棵 \(m\) 条边的生成树势必存在一棵与其离差相同的 \(m - 1\) 边的生成森林,所以直接做 \(n - 2\) 条边的最大生成森林即可。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
constexpr int N = 2e5 + 10, M = 5e5 + 10;
int n, m, eds;
ll res, ans;
struct Edge {
int u, v, w;
bool operator<(const Edge &rhs) const {return w > rhs.w;}
} e[M];
namespace DSU {
int fa[N], sz[N];
inline void init() {iota(fa + 1, fa + n + 1, 1); memset(sz + 1, 0, n << 2);}
inline int find(int x) {return x == fa[x] ? x : fa[x] = find(fa[x]);}
inline bool merge(int x, int y) {
int fx = find(x), fy = find(y);
if (fx == fy) return 0;
if (sz[fx] < sz[fy]) swap(fx, fy);
fa[fy] = fx, sz[fx] += sz[fy];
return 1;
}
}
bool check(int C) {
DSU::init();
int cnt = res = 0, wcnt = 0, l = 1, r = m;
while (l <= r) {
Edge cur; bool white = 0;
if (e[l].w > C - e[r].w) cur = e[l++];
else cur = e[r--], white = 1, cur.w = C - cur.w;
if (DSU::merge(cur.u, cur.v)) {
res += cur.w, wcnt += white;
if (++cnt == (eds << 1)) break;
}
}
res -= 1ll * eds * C;
return wcnt >= eds;
}
int main() {
ios_base::sync_with_stdio(0); cin.tie(nullptr), cout.tie(nullptr);
cin >> n >> m; eds = (n - 1) >> 1; if (!eds) {cout << 0; return 0;}
for (int i = 1; i <= m; i++) cin >> e[i].u >> e[i].v >> e[i].w;
sort(e + 1, e + m + 1);
int l = 0, r = 1e9;
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid)) r = mid - 1, ans = res;
else l = mid + 1;
}
cout << ans;
return 0;
}