zoukankan      html  css  js  c++  java
  • @hdu


    @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
    对于每组数据,输出最小生成树的权值和。

    Sample Input
    1
    3
    2 4
    3 1
    5 2
    Sample Output
    27

    @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) 搞定一次查询最小邻边了。

    套上生成树的复杂度为 O(nlog^2n)。但由于 boruvka 一般跑不满所以常数小,可以通过。

    @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
    ", ans);
    }
    int main() {
        int T; scanf("%d", &T);
        while( T-- ) solve();
    }
    

    @details@

    其实。。。我一直卡在把询问和横坐标分开排序这一点上。。。
    一直在想怎么才能避免在凸包上二分。。。

  • 相关阅读:
    169. Majority Element
    283. Move Zeroes
    1331. Rank Transform of an Array
    566. Reshape the Matrix
    985. Sum of Even Numbers After Queries
    1185. Day of the Week
    867. Transpose Matrix
    1217. Play with Chips
    766. Toeplitz Matrix
    1413. Minimum Value to Get Positive Step by Step Sum
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11789025.html
Copyright © 2011-2022 走看看