题意
给定一个序列,有q组询问,询问l~r中
最小的值。(x可以任选)
思路
首先,给定一个从小到大的数列x1,x2,……,xn,设x是从x1到xn与其绝对差之和最小的数,则显然x位于x1与xn之间。
那么,由于x1,xn与它们之间的任意一点的距离之和都相等,且都等于xn-x1,因此接下来可以不考虑x1与xn,而考虑剩下的从x2到x[n-1]的数,同样显然有x必然位于x2和x[n-1]之间。
依次类推,最后得出的结论是x就是该数列中间的那个数,或者是中间的那两个数之一,而这个数就是中位数。
结论:数列的中位数就是该数列各个数与其绝对差之和最小的数。
(引用自dalao博客)
所以现在问题变成了如何求出上图中的值。
直接去绝对值就会发现,
a
n
s
=
比
中
位
数
大
的
数
的
和
−
比
中
位
数
小
的
数
的
和
ans=比中位数大的数的和-比中位数小的数的和
ans=比中位数大的数的和−比中位数小的数的和。
由于空间限制,这道题不可以用主席树,只能用划分树啦。
在建树时,多维护一个sum,表示划分的该区间起始到下标 i 的数的前缀和。
查询时,可直接查询中位数,顺便求出ans。
查询时引入四个变量:
特别声明一下,L、R表示当前查询的区间左右端点,l、r表示询问的区间的左右端点!
int l1 = toleft[dep][l-1] - toleft[dep][L-1];//L~l中前一半个数
int r1 = l-L-l1;// L~l中后一半个数
int l2 = cnt;// l~r中前一半个数
int r2 = r-l+1-cnt;//l~r中后一半个数
这四个变量均表示个数,便于之后计算ans。
以当前查询的中位数在左子树中为例,有以下程序:
if(cnt >= k){
/*********************************************************************/
if(r2 > 0)
if(r1 > 0) ans += sum[dep+1][mid+r1+r2] - sum[dep+1][mid+r1];
else ans += sum[dep+1][mid + r2];
/********************************************************************/
int newl=L+l1;
int newr=newl+cnt-1;
return query(L,mid,newl,newr,dep+1,k);
}
比正常划分树新增部分,即/*********************/中间的部分,为维护ans(显然哈哈哈)。由于中位数在左子树中,右子树中的数比中位数大,所以ans应该加上这些数。如果r2>0,那么就表示有数比中位数大,并且在询问区间左右端点内,答案需要加上这部分数;而如果r1>0,就表示在查询区间左端点到询问区间左端点内也有数比中位数大的,答案需要减去这一部分数。
(可能我说的有点凌乱,其实自己画个图,小小琢磨一下,就懂啦)
代码
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
const int MAXN=100010;
int tree[20][MAXN];
int sorted[MAXN];
long long sum[20][MAXN],ans;//存下本小节1~i的前缀和
int toleft[20][MAXN];
void build(int l,int r,int dep){
if(l == r){
sum[dep][l] = tree[dep][l];
return;
}
int mid = (l+r)>>1;
int same = mid-l+1;
for(int i = l; i <= r; ++ i){
if(tree[dep][i] < sorted[mid]) same --;
sum[dep][i] = tree[dep][i];
if(i != l) sum[dep][i] += sum[dep][i-1];
}
int lpos = l;
int rpos = mid+1;
for(int i = l; i <= r; ++ i){
if(tree[dep][i] < sorted[mid])
tree[dep+1][lpos ++] = tree[dep][i];
else if(tree[dep][i] == sorted[mid] && same > 0)
tree[dep+1][lpos ++] = tree[dep][i], same --;
else
tree[dep+1][rpos ++] = tree[dep][i];
toleft[dep][i] = toleft[dep][l-1]+lpos-l;
}
build(l,mid,dep+1);
build(mid+1,r,dep+1);
}
int query(int L,int R,int l,int r,int dep,int k){
if(l == r) return tree[dep][l];
int mid = (L+R)>>1;
int cnt = toleft[dep][r]-toleft[dep][l-1];
int l1 = toleft[dep][l-1] - toleft[dep][L-1];//L~l中前一半个数
int r1 = l-L-l1;// L~l中后一半个数
int l2 = cnt;// l~r中前一半个数
int r2 = r-l+1-cnt;//l~r中后一半个数
//因为是绝对值,前减后加
if(cnt >= k){
if(r2 > 0){
if(r1 > 0) ans += sum[dep+1][mid+r1+r2] - sum[dep+1][mid+r1];//把后一半都加上
else ans += sum[dep+1][mid + r2];
}
int newl=L+l1;
int newr=newl+cnt-1;
return query(L,mid,newl,newr,dep+1,k);
}
else{
if(l2 > 0){
if(l1 > 0) ans -= sum[dep+1][L-1+l1+l2] - sum[dep+1][L-1+l1];
else ans -= sum[dep+1][L-1+l2];
}
int newr=r+toleft[dep][R]-toleft[dep][r];
int newl=newr-(r-l-cnt);
return query(mid+1,R,newl,newr,dep+1,k-cnt);
}
}
int main(){
int n,m,ct=0;
int s,t,k,Tcase;
scanf("%d",&Tcase);
while(Tcase --){
printf("Case #%d:\n",++ ct);
scanf("%d",&n);
memset(tree,0,sizeof(tree)); //这个必须
for(int i = 1; i <= n; ++ i){ //从1开始
scanf("%d", &tree[0][i]);
sorted[i] = tree[0][i];
}
sort(sorted+1, sorted+n+1);
build(1,n,0);
scanf("%d",&m);
while(m --){
scanf("%d%d",&s,&t);
++ s,++ t;
ans = 0;
long long mid = query(1,n,s,t,0,(t-s)/2+1);//取中位数
if((t-s+1)%2 == 0) ans -= mid;
printf("%lld\n",ans);
}
printf("\n");
}
return 0;
}