zoukankan      html  css  js  c++  java
  • HDU 3473 Minimum Sum (划分树)


    题意:给定一个数组,有Q次的询问,每次询问的格式为(l,r),表示求区间中一个数x,使得sum = sigma|x - xi|最小(i在[l,r]之间),输出最小的sum。

    思路:本题一定是要O(nlogn)或更低复杂度的算法。首先很容易得出这个x的值一定是区间(l,r)的中位数的取值,排序之后,也就是假设区间(l,r)长度为len ,则中位数就是该区间的第(r - l) / 2 - 1小的元素,求一个区间的第K小元素的算法很自然地会想到划分树, 而且划分树的查询复杂度为:O(logn),正好可以解决此题。


    算法确定了之后就是具体的实现过程了,普通的划分树求的是区间内的第k小的元素,而这题是要求差值,也就是说我们不但要求出区间的第k小的元素,还要求出所有比中位数小的数 lsum,当然比中位数大的数的和可以根据区间的数的总和和lsum求得,因此不需要额外求。这样我们只需要在划分树建树的时候增加一个lsum[ ][i] 数组就可以了, 这个数组保存的是,在step层,在i前面被划分到左子树的元素之和。这样我们就可以求出最后的解了。


    #include <iostream>
    #include <algorithm>
    #include <cmath>
    #include<functional>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <string>
    #include <vector>
    #include <set>
    #include <queue>
    #include <stack>
    #include <climits>//形如INT_MAX一类的
    #define MAX 100005
    #define INF 0x7FFFFFFF
    #define L(x) x << 1
    #define R(x) x << 1 | 1
    using namespace std;
    
    struct Seg_Tree {
        int l,r,mid;
    } tr[MAX*4];
    int sorted[MAX];
    int lef[20][MAX];
    int val[20][MAX];
    __int64 lsum[20][MAX];
    __int64 sum[MAX];
    __int64 summ;
    
    void build(int l,int r,int step,int x) {
        tr[x].l = l;
        tr[x].r = r;
        tr[x].mid = (l + r) >> 1;
        if(tr[x].l == tr[x].r) return ;
        int mid = tr[x].mid;
        int lsame = mid - l + 1;//lsame表示和val_mid相等且分到左边的
        for(int i = l ; i <= r ; i ++) {
            if(val[step][i] < sorted[mid]) {
                lsame --;//先假设左边的数(mid - l + 1)个都等于val_mid,然后把实际上小于val_mid的减去
            }
        }
        int lpos = l;
        int rpos = mid + 1;
        int same = 0;
        for(int i = l ; i <= r ; i ++) {
            if(i == l) {
                lef[step][i] = 0;//lef[i]表示[ tr[x].l , i ]区域里有多少个数分到左边
                lsum[step][i] = 0;
            } else {
                lef[step][i] = lef[step][i-1];
                lsum[step][i] = lsum[step][i-1];
            }
            if(val[step][i] < sorted[mid]) {
                lef[step][i] ++;
                lsum[step][i] += val[step][i];
                val[step + 1][lpos++] = val[step][i];
            } else if(val[step][i] > sorted[mid]) {
                val[step+1][rpos++] = val[step][i];
            } else {
                if(same < lsame) {//有lsame的数是分到左边的
                    same ++;
                    lef[step][i] ++;
                    lsum[step][i] += val[step][i];
                    val[step+1][lpos++] = val[step][i];
                } else {
                    val[step+1][rpos++] = val[step][i];
                }
            }
        }
        build(l,mid,step+1,L(x));
        build(mid+1,r,step+1,R(x));
    }
    
    int query(int l,int r,int k,int step,int x) {
        if(l == r) {
            return val[step][l];
        }
        int s;//s表示[l , r]有多少个分到左边
        int ss;//ss表示 [tr[x].l , l-1 ]有多少个分到左边
        __int64 tmp = 0;
        if(l == tr[x].l) {
            tmp = lsum[step][r];
            s = lef[step][r];
            ss = 0;
        } else {
            tmp = lsum[step][r] - lsum[step][l-1];
            s = lef[step][r] - lef[step][l-1];
            ss = lef[step][l-1];
        }
        if(s >= k) {//有多于k个分到左边,显然去左儿子区间找第k个
            int newl = tr[x].l + ss;
            int newr = tr[x].l + ss + s - 1;//计算出新的映射区间
            return query(newl,newr,k,step+1,L(x));
        } else {
            summ += tmp;
            int mid = tr[x].mid;
            int bb = l - tr[x].l - ss;//bb表示 [tr[x].l , l-1 ]有多少个分到右边
            int b = r - l + 1 - s;//b表示 [l , r]有多少个分到右边
            int newl = mid + bb + 1;
            int newr = mid + bb + b;
            return query(newl,newr,k-s,step+1,R(x));
        }
    }
    
    void solve(int l,int r) {
        l ++; r ++;
        summ = 0;
        int k = (r - l) / 2 + 1;
        __int64 mid = query(l,r,k,0,1);
        __int64 ans = (k - 1) * mid - summ;
        ans += sum[r] - sum[l-1] - summ - (r - l - k + 2) * mid;
        printf("%I64d
    ",ans);
    }
    
    int n,m,l,r;
    int main() {
        int T;
        cin >> T;
        int ca = 1;
        while(T--) {
            scanf("%d",&n);
            memset(sum,0,sizeof(sum));
            for(int i=1; i<=n; i++) {
                scanf("%d",&val[0][i]);
                sorted[i] = val[0][i];
                sum[i] = sum[i-1] + val[0][i];
            }
            sort(sorted+1,sorted+1+n);
            build(1,n,0,1);
            scanf("%d",&m);
            printf("Case #%d:
    ",ca++);
            for(int i=0; i<m; i++) {
                scanf("%d%d",&l,&r);
                solve(l,r);
            }
            puts("");
        }
    }


  • 相关阅读:
    "V租房"搭建微信租房平台,让租房人发起求租需求并接收合适房源回复,提高租房效率 | 36氪
    金融街
    Jsensation | 氪加
    Polyvore
    周翔宇_百度百科
    鸵鸟心态
    新闻:型牌男装:网上订服装,如何将返修率降到5个点以下 | IT桔子
    【案例】舒邑:一个女装品牌的奇葩打法-@i黑马
    专访OPPO李紫贵:ColorOS用户过千万 软硬融合生态版图初现
    关于我们-EIBOA易博男装-互联网品质男装品牌-在线销售男士西服,衬衫,外套,西裤,领带|全场免运费,30天退换货保障
  • 原文地址:https://www.cnblogs.com/keanuyaoo/p/3306392.html
Copyright © 2011-2022 走看看