@hdu - 6329@ Problem K. Transport Construction


@description@

給定 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

@solution@

徹底圖的最小生成樹,套路般的 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

@accepted code@

#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();
}

@details@

其實。。。我一直卡在把詢問和橫座標分開排序這一點上。。。
一直在想怎麼才能避免在凸包上二分。。。io

相關文章
相關標籤/搜索