【划分树】HDU 3473

题意就是求[l,r]中位数与其余元素差的绝对值再求和

方法:用划分树求出中位数,询问的同时求出中位数左边元素和还有左边元素个数,因此这里需要增加一个数组suml[dep][i]表示深度为dep的一段区间里划分到左边的元素和,求出来后就可以算出右边元素和还有右边元素个数,最后的结果就是ans = rs-ls+mid*(lcnt-rcnt),具体看代码和注释。

注意有可能溢出,所以凡是跟元素和有关的变量都要long long!在这里wa了几次.....囧

#define N 100005
int a[N], as[N];//原数组,排序后数组
int sum[20][N];//记录第i层的1~j划分到左子树的元素个数(包括j)
int tree[20][N];//记录第i层元素序列
LL suml[20][N];//凡是跟suml有关的变量都要long long!
int n, m;
void build(int c, int l, int r){
    int i, mid = (l + r) >> 1, lm = mid - l + 1, lp = l, rp = mid + 1;
    for (i = l; i <= mid; i++){
        if (as[i] < as[mid]){
            lm--;//先假设左边的(mid - l + 1)个数都等于as[mid],然后把实际上小于as[mid]的减去
        }
    }
    for (i = l; i <= r; i++){
        if (i == l){
            sum[c][i] = 0;//sum[i]表示[l, i]内有多少个数分到左边,用DP来维护
            suml[c][i] = 0;
        }else{
            sum[c][i] = sum[c][i-1];
            suml[c][i] = suml[c][i-1];
        }
        if (tree[c][i] == as[mid]){
            if (lm){
                lm--;
                sum[c][i]++;
                suml[c][i] += tree[c][i];
                tree[c + 1][lp++] = tree[c][i];
            }else
                tree[c + 1][rp++] = tree[c][i];
        } else if (tree[c][i] < as[mid]){
            sum[c][i]++;
            suml[c][i] += tree[c][i];
            tree[c + 1][lp++] = tree[c][i];
        } else{
            tree[c + 1][rp++] = tree[c][i];
        }
    }
    if (l == r)return;
    build(c + 1, l, mid);
    build(c + 1, mid + 1, r);
}
LL ls,rs;//中位数左边和,右边和
int lcnt,rcnt;//中位数左边个数,右边个数
int query(int c, int l, int r, int ql, int qr, int k){
    int s;
    int ss;
    LL x,xx;
    int mid = (l + r) >> 1;
    if (l == r){
        return tree[c][l];
    }
    if (l == ql){
    s = 0;
    ss = sum[c][qr];
    x = 0;
    xx = suml[c][qr];
    }else{
        s = sum[c][ql - 1];
        ss = sum[c][qr] - s;
        x = suml[c][ql-1];
        xx = suml[c][qr] - x;
    }
    if (k <= ss){
        return query(c + 1, l, mid, l + s, l + s + ss - 1, k);
    }else{//划分到左边的都比中位数小
        ls += xx;
        lcnt += ss;
        return query(c + 1, mid + 1, r, mid - l + 1 + ql - s, mid - l + 1 + qr - s - ss,k - ss);
    }
}
LL lin[N];//前i个的和
int main(){
    int i, j, k;
    int ca;
    scanf("%d",&ca);
    int tt = 1;
    while(ca--){
        int n;
        scanf("%d",&n);
        lin[0] = 0;
        for(i=1;i<=n;i++){
            scanf("%d",&a[i]);
            as[i] = tree[0][i] = a[i];
            lin[i] = lin[i-1] + a[i];
        }
        sort(as+1,as+1+n);
        build(0,1,n);
        scanf("%d",&m);
        printf("Case #%d:\n",tt++);
        while(m--){
            int x,y;
            scanf("%d%d",&x,&y);
            x++,y++;
            k = (y-x)/2+1;
            ls = rs = 0;
            lcnt = rcnt = 0;
            int mid = query(0,1,n,x,y,k);
            rs = lin[y]-lin[x-1]-ls-mid;//右边和
            rcnt = (y-x)-lcnt;//右边个数
            if(x == y)rs = ls = 0,lcnt = rcnt = 0;
            LL ans = rs-ls+mid*(lcnt-rcnt);
            printf("%I64d\n",ans);
        }
        printf("\n");
    }
    return 0;
}


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值