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\n",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:\n",ca++);

        for(int i=0; i<m; i++) {

            scanf("%d%d",&l,&r);

            solve(l,r);

        }

        puts("");

    }

}


 

 

你可能感兴趣的:(HDU)