MinimumSum
描述
已知含有N个正整数的数列 x0, x1 ... xN-1。对于每个询问的区间[l, r](包含左右端点),在区间内找出一个数x使得尽量小。
输入
每组数据中:
第一行是数列长度N(1 <= N <= 100,000)
第二行给出数列中的N个数xi (1 <= x <=1,000, 000,000)
第三行为询问数Q(1 <= Q <= 100,000),每个询问给出区间的左右端点l, r(0 <= l <= r < N)
输出
输出Q行,每一行是询问区间[l,r]的.最小值
输入样例1:
5
3 6 2 2 4
2
1 4
0 2
输出样例1:
6
4
输入样例2:
2
7 7
2
0 1
1 1
输出样例2:
0
0
数据规模
30%的数据,N<=1000,Q<=1000。
所有数据,N<=100000,Q<=100000, 1<= xi <= 1,000, 000,000, 0 <= l<= r < N
先反省一下,又是少开一个long long,结果就是调了整整一天
稍稍动点脑筋就可以知道我们选的基准 X 肯定是中位数!
好了,题目就转化成求第K大了,所以划分树
注意那个公式有绝对值,所以我们就不能简单的化简为| N*x - Sum(Xi,...,Xj) | ,为什么自己想想
所以我们要计算那个公式,必须一个一个算,极限情况O(N),所以还是要T,所以公式的代价我们需要降下来!
我们发现,虽然不能直接合并,但是可以把 A*x - Sum(比X小的) + Sum(比X大的) - B*x [ A + B = N ]
所以方法有两个
①划分数+树状数组/线段树
这个说实话没写过,题解上说的方法,用划分树求第K大的数 X ,然后用线段树(树状数组)维护比 X 小的数的和,在维护比X大的数的和
②划分树
我就用的这个方法
在划分树的基础上再多维护一个sum,表示进入左儿子的的和,至于右儿子的和我们可以用前缀和算出总和减去左儿子的和
维护出来后在查询的时候额外维护处左儿子的和以及左儿子的个数
测评情况(cena)
①考试30分,写的朴素
②后来改了一个上午,结果是多维护的那个sum开的int,而应该开成long long
C++ AC Code
/*http://blog.youkuaiyun.com/jiangzh7
By Jiangzh*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using std::sort;
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
const int N=100000+10;
const int inf=0x3f3f3f3f;
typedef long long LL;
int n,m;
int a[N];
LL sum[N];
int sorted[N];
struct node{int val,left;LL sum;}val[50][N];//一定要注意sum开long long啊!!!
void read()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
sum[i]=sum[i-1]+a[i];
sorted[i]=a[i];
val[0][i].val=a[i];
}
sort(sorted+1,sorted+1+n);
}
void build_tree(int d,int l,int r)
{
if(l==r) return;
int m=(l+r)>>1;
int same=m-l+1;
int lcnt=l,rcnt=m+1;
for(int i=l;i<=r;i++) if(val[d][i].val<sorted[m]) same--;
for(int i=l;i<=r;i++)
{
int flag=0;
if(val[d][i].val<sorted[m]||(val[d][i].val==sorted[m]&&same>0))
{
flag=1;
if(val[d][i].val==sorted[m]) same--;
val[d+1][lcnt++]=val[d][i];
}
else{
val[d+1][rcnt++]=val[d][i];
}
val[d][i].left=val[d][i-1].left+flag;
val[d][i].sum=val[d][i-1].sum+((flag)?val[d][i].val:0);//额外维护的sum
}
build_tree(d+1,l,m);
build_tree(d+1,m+1,r);
}
int query(int d,int l,int r,int x,int y,int k,LL &lsum,LL &lnum)
{
if(l==r)
{
lsum=lnum=0;
return val[d][l].val;
}
int m=(l+r)>>1;
int lx=val[d][x-1].left-val[d][l-1].left;//[l,x-1] to left
int ly=val[d][y].left-val[d][x-1].left;//[x,y] to left
int rx=(x-1-l+1)-lx;//[l,x-1] to right
int ry=(y-x+1)-ly;//[x,y] to right
int res;
if(ly>=k) res=query(d+1,l,m,l-1+lx+1,l-1+lx+ly,k,lsum,lnum);
else{
res=query(d+1,m+1,r,m+1-1+rx+1,m+1-1+rx+ry,k-ly,lsum,lnum);
lnum+=ly; lsum+=val[d][y].sum-val[d][x-1].sum;
//要查找的第K大进入了右儿子,那么此时就可以累加上进入左儿子的信息
}
return res;
}
void work()
{
build_tree(0,1,n);
int m;scanf("%d",&m);
while(m--)
{
int x,y;scanf("%d%d",&x,&y);x++;y++;
LL lsum=0,lnum=0;
LL rsum=sum[y]-sum[x-1],rnum=y-x+1;
int k=((y-x+1)&1)?((y-x+1)/2+1):((y-x+1)/2);
int sta=query(0,1,n,x,y,k,lsum,lnum);
rsum-=lsum;rnum-=lnum;
LL res=(LL)sta*lnum-lsum + rsum-(LL)sta*rnum;
printf("%I64d\n",res);//linux下记得改成lld就行了
}
}
int main()
{
freopen("MinimumSum.in","r",stdin);
freopen("MinimumSum.out","w",stdout);
read();
work();
return 0;
}