Boruvka算法解决某些问题超级好用。
这些问题形如,给你n个点,每个点有点权,任意两个点之间有边权,边权为两个点权用过某种计算方式得出。
求最小生成树。
通常用O(log n)的时间可以找到与点i连边的边权最小的j。
我们考虑这样一个求最小生成树的算法:
考虑维护当前的连通块(初始每个点为独立的一个连通块)
对每个连通块,找到一条与该连通块相连的,且另一端点不在此连通块中的边权最小的边。
将所有的这些边都加入最小生成树,注意,当加入一条边时需判断该边的两端点是否在同一连通块。
重复若干遍上述操作,直到图连通。
这个算法叫Boruvka算法。
复杂度分析:每次连通块个数至少减半,则复杂度为O((n+m)logn),还有个并查集假装是O(1)
但是此算法通常不用于求裸的最小生成树(Kruskal多好用)
可是在解决上述问题时,往往有奇效。
我们发现,我们只需要求出与每个连通块相连的边权最小的边即可,在这种类型的题目中,这个东西复杂度一般为O(n log n)
所以我们就可以在O(n log^2 n)的复杂度下解决此类问题。
Atcoder keyence2019 E
#include <cstdio> #include <iostream> #include <cstring> #define MN 201000 typedef long long ll; int fa[MN]; int c1[MN], c2[MN]; int Min[MN]; ll D; int a[MN]; int X[MN], Y[MN]; int x[MN], y[MN]; ll ans = 0; int bl; int n; int Abs(int a) {return a > 0 ? a : -a;} ll F(int i) {return i ? i * D + a[i] : 1e18;} ll G(int i) {return i ? -i * D + a[i] : 1e18;} ll H(int i, int j) {if(i == 0 || j == 0) return 1e18; return Abs(i - j) * D + a[i] + a[j];} void add(int *c, int x, int v, int type) { for(int i = x; i <= n; i += i & -i) { if(!type) if(F(v) < F(c[i])) c[i] = v; if( type) if(G(v) < G(c[i])) c[i] = v; } } int query(int *c, int x, int type) { int ans = 0; for(int i = x; i; i -= i & -i) { if(!type) if(F(c[i]) < F(ans)) ans = c[i]; if( type) if(G(c[i]) < G(ans)) ans = c[i]; } return ans; } int Find(int x) {return fa[x] == x ? x : fa[x] = Find(fa[x]);} void solve() { memset(x, 0, sizeof x); memset(y, 0, sizeof y); memset(c1, 0, sizeof c1); memset(c2, 0, sizeof c2); memset(Min, 0, sizeof Min); for(int i = 1; i <= n; i++) { int u = Find(i); int x = query(c1, u - 1, 1); int y = query(c2, n - u, 1); if(G(x) > G(y)) x = y; if(H(x, i) <= H(Min[i], i)) Min[i] = x; add(c1, u, i, 1); add(c2, n - u + 1, i, 1); } memset(c1, 0, sizeof c1); memset(c2, 0, sizeof c2); for(int i = n; i >= 1; i--) { int u = Find(i); int x = query(c1, u - 1, 0); int y = query(c2, n - u, 0); if(F(x) > F(y)) x = y; if(H(x, i) <= H(Min[i], i)) Min[i] = x; add(c1, u, i, 0); add(c2, n - u + 1, i, 0); } for(int i = 1; i <= n; i++) { int u = Find(i); if(H(i, Min[i]) < H(x[u], y[u])) x[u] = i, y[u] = Min[i]; } int tot = 0; for(int i = 1; i <= n; i++) { int u = Find(i); ++tot; X[tot] = x[u]; Y[tot] = y[u]; } for(int i = 1; i <= tot; i++) { int x = Find(X[i]), y = Find(Y[i]); if(x == y) continue; ans += H(X[i], Y[i]); fa[x] = y; bl--; } } int main() { scanf("%d%lld", &n, &D); for(int i = 1; i <= n; i++) scanf("%d", &a[i]), fa[i] = i; bl = n; while(bl > 1) solve(); printf("%lld ", ans); }