给定 n 个点,第 i 个点位于 (xi, yi)。
在第 i 个点与第 j 个点之间建边费用为 xi*xj + yi*yj。
求最小生成树。算法
Input
第一行一个整数 T (1≤T≤2000),表示数据组数。
每组数据给定一个整数 n(2≤n≤100000),表示点数。保证 ∑n≤10^6。
接下来 n 行,每行两个整数 xi, yi(1≤xi,yi≤10^6), 描述一个点的坐标。注意能够点能够重合。优化
Output
对于每组数据,输出最小生成树的权值和。spa
Sample Input
1
3
2 4
3 1
5 2
Sample Output
27code
彻底图的最小生成树,套路般的 boruvka 算法(能够本身百度一下)。
boruvka 算法的其余部分都是套路,主要是考虑怎么求一个连通块的最小邻边。排序
注意到点积 \(x_i*x_j + y_i*y_j\) 其实能够写成斜率优化形式的式子 \(y_i*(\frac{x_i}{y_i}*x_j + y_j)\)。当 i 固定时至关于求 \(\frac{x_i}{y_i}*x_j + y_j\) 的最小值。
而后就能够转成维护凸包了。递归
可是咱们还须要排除同一连通块中的点,而众所周知凸包很难实现删除。
能够考虑 cdq 分治(不清楚是否是,不过很像)。将同一连通块的点视做同一颜色,对颜色进行分治。
具体来讲,对于区间 [l, r] 中的颜色,咱们只考虑 [l, mid] 对 [mid + 1, r] 的贡献与 [mid + 1, r] 对 [l, mid] 的贡献。
分治时分开维护询问与点,经过先递归再归并排序实现横坐标与询问的有序。而后就能够 O(nlogn) 搞定一次查询最小邻边了。ip
套上生成树的复杂度为 O(nlog^2n)。但因为 boruvka 通常跑不满因此常数小,能够经过。it
#include <cstdio> #include <iostream> #include <algorithm> using namespace std; #define mp make_pair #define fi first #define se second typedef long long ll; typedef pair<ll, int> pli; const int MAXN = 100000; const ll INF = (1LL<<60); struct point{ int x, y, c; point(int _x=0, int _y=0, int _c=0):x(_x), y(_y), c(_c) {} }pnt[MAXN + 5], qry[MAXN + 5], tmp[MAXN + 5]; double f(point p) { return - 1.0 * p.x / p.y; } bool cmp1(point a, point b) { return a.c == b.c ? (a.x == b.x ? a.y < b.y : a.x < b.x) : a.c < b.c; } bool cmp2(point a, point b) { return a.c == b.c ? f(a) < f(b) : a.c < b.c; } double slope(point a, point b) { if( a.x == b.x ) { if( b.y >= a.y ) return INF; else return -INF; } else return 1.0 * (b.y - a.y) / (b.x - a.x); } int que[MAXN + 5]; pli lnk[MAXN + 5]; void solve(int l, int r, int L, int R) { if( L == R ) return ; int M = (L + R) >> 1, m; for(int i=l;i<=r;i++) if( pnt[i].c <= M ) m = i; solve(l, m, L, M), solve(m + 1, r, M + 1, R); int s = 1, t = 0; for(int i=l;i<=m;i++) { while( s < t && slope(pnt[que[t-1]], pnt[que[t]]) >= slope(pnt[que[t]], pnt[i]) ) t--; que[++t] = i; } for(int i=m+1;i<=r;i++) { while( s < t && slope(pnt[que[s]], pnt[que[s+1]]) <= f(qry[i]) ) s++; int j = que[s]; lnk[qry[i].c] = min(lnk[qry[i].c], mp(1LL*pnt[j].x*qry[i].x + 1LL*pnt[j].y*qry[i].y, pnt[j].c)); } s = 1, t = 0; for(int i=m+1;i<=r;i++) { while( s < t && slope(pnt[que[t-1]], pnt[que[t]]) >= slope(pnt[que[t]], pnt[i]) ) t--; que[++t] = i; } for(int i=l;i<=m;i++) { while( s < t && slope(pnt[que[s]], pnt[que[s+1]]) <= f(qry[i]) ) s++; int j = que[s]; lnk[qry[i].c] = min(lnk[qry[i].c], mp(1LL*pnt[j].x*qry[i].x + 1LL*pnt[j].y*qry[i].y, pnt[j].c)); } int p = l, q = m + 1, k = l; while( p <= m && q <= r ) { if( pnt[p].x < pnt[q].x ) tmp[k++] = pnt[p++]; else tmp[k++] = pnt[q++]; } while( p <= m ) tmp[k++] = pnt[p++]; while( q <= r ) tmp[k++] = pnt[q++]; for(int i=l;i<=r;i++) pnt[i] = tmp[i]; p = l, q = m + 1, k = l; while( p <= m && q <= r ) { if( f(qry[p]) < f(qry[q]) ) tmp[k++] = qry[p++]; else tmp[k++] = qry[q++]; } while( p <= m ) tmp[k++] = qry[p++]; while( q <= r ) tmp[k++] = qry[q++]; for(int i=l;i<=r;i++) qry[i] = tmp[i]; } int id[MAXN + 5], fa[MAXN + 5], clr[MAXN + 5]; int find(int x) { return fa[x] = (fa[x] == x ? x : find(fa[x])); } bool unite(int x, int y) { int fx = find(x), fy = find(y); if( fx != fy ) { fa[fx] = fy; return true; } else return false; } int x[MAXN + 5], y[MAXN + 5], n; void solve() { scanf("%d", &n); for(int i=1;i<=n;i++) scanf("%d%d", &x[i], &y[i]), fa[i] = i; ll ans = 0; while( true ) { int cnt = 0; for(int i=1;i<=n;i++) if( fa[i] == i ) id[clr[i] = (++cnt)] = i; if( cnt == 1 ) break; for(int i=1;i<=n;i++) clr[i] = clr[find(i)]; for(int i=1;i<=n;i++) pnt[i] = qry[i] = point(x[i], y[i], clr[i]); sort(pnt + 1, pnt + n + 1, cmp1); sort(qry + 1, qry + n + 1, cmp2); for(int i=1;i<=cnt;i++) lnk[i] = mp(INF, -1); solve(1, n, 1, cnt); for(int i=1;i<=cnt;i++) if( unite(id[i], id[lnk[i].se]) ) ans += lnk[i].fi; } printf("%lld\n", ans); } int main() { int T; scanf("%d", &T); while( T-- ) solve(); }
其实。。。我一直卡在把询问和横坐标分开排序这一点上。。。
一直在想怎么才能避免在凸包上二分。。。io