HDU 3473 Minimum Sum

/*题意:给出n个数x1,x2...xn.进行m次操作,每次给出一个区间[l ,r].求出xi属于区间[l, r]使得该区间的每个数与xi的差之和最小,并求出该和。解决方法:划分树。*/
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>

using namespace std;


const int MAXN=200010;


int tree[20][MAXN];
int sorted[MAXN];
int toLeft[20][MAXN];
long long sum[20][MAXN];

void build(int level,int left,int right){
    if(left==right){
      sum[level][left]=tree[level][left];
      return;
    }
    int mid=(left+right)>>1;
    int suppose=mid-left+1;//suppose表示等于中间值且到左边的数的个数
    for(int i=left;i<=right;i++){
      if(tree[level][i]<sorted[mid])
      suppose--;
      sum[level][i]=tree[level][i];
      if(i>left)sum[level][i]+=sum[level][i-1];
    }
    int lpos=left;
    int rpos=mid+1;
    for(int i=left;i<=right;i++){
      if(tree[level][i]<sorted[mid])//去左边
      {
        tree[level+1][lpos++]=tree[level][i];
      }
      else if(tree[level][i]==sorted[mid]&&suppose>0)//去左边
      {
        tree[level+1][lpos++]=tree[level][i];
        suppose--;
      }
      //去右边
      else tree[level+1][rpos++]=tree[level][i];
      toLeft[level][i]=toLeft[level][left-1]+lpos-left;//从1到i放左边的个数
    }
    build(level+1,left,mid);//递归建树
    build(level+1,mid+1,right);
}


long long ans;


int query(int level,int left,int right,int qleft,int qright,int k){
    if(qleft==qright)return tree[level][qleft];
    int mid=(left+right)>>1;
    int cnt=toLeft[level][qright]-toLeft[level][qleft-1];
    int ss=toLeft[level][qleft-1]-toLeft[level][left-1];
    int ee=qleft-left-ss;
    int s=toLeft[level][qright]-toLeft[level][qleft-1];
    int e=qright-qleft+1-s;
    if(cnt>=k){
      if(e>0){
        if(ee>0)ans+=sum[level+1][mid+e+ee]-sum[level+1][mid+ee];
        else ans+=sum[level+1][mid+e];
      }
      //left+查询区间前去左边的数的个数
      int newl=left+toLeft[level][qleft-1]-toLeft[level][left-1];
      //左端点+查询区间会分入左边的数的个数
      int newr=newl+cnt-1;
      return query(level+1,left,mid,newl,newr,k);//注意
    }
    else{
      if(s>0){
        if(ss>0) ans-=sum[level+1][left+ss+s-1]-sum[level+1][left+ss-1];
        else ans-=sum[level+1][left+s-1];
      }
      //qright+区间后分入左边的数的个数
      int newr=qright+toLeft[level][right]-toLeft[level][qright];
      //右端点减去区间分入右边的数的个数
      int newl=newr-(qright-qleft-cnt);
      return query(level+1,mid+1,right,newl,newr,k-cnt);//注意
    }
}

int main(){
    int T;
    int n,m;
    scanf("%d",&T);
    int iCase=0;
    while(T--){
      scanf("%d",&n);
      for(int i=1;i<=n;i++){
        scanf("%d",&tree[0][i]);
        sorted[i]=tree[0][i];
      }
      sort(sorted+1,sorted+1+n);
      build(0,1,n);
      printf("Case #%d:\n",++iCase);
      scanf("%d",&m);
      while(m--){
        int ql,qr,k;
        scanf("%d%d",&ql,&qr);
        ++ql,++qr;
        ans=0;
        k=(qr-ql)/2+1;
        int tmp=query(0,1,n,ql,qr,k);
        if((qr-ql+1)%2==0) ans-=tmp;
        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、付费专栏及课程。

余额充值