• @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@

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

  • 相关阅读:
    【译】x86程序员手册30-8.2 I/O指令
    【译】x86程序员手册29-第8章 输入输出
    【译】x86程序员手册28-7.7任务地址空间
    【译】x86程序员手册27-7.6任务链
    【译】x86程序员手册26-7.5任务切换
    【译】x86程序员手册25-7.1任务状态段
    【译】x86程序员手册23-6.5组合页与段保护
    【译】x86程序员手册24-第7章 多任务
    【译】x86程序员手册22-6.4页级保护
    WPF Blend Grid 布局
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11789025.html
Copyright © 2020-2023  润新知