題意:給定一個數組,有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("");
}
}