点击打开链接
题意:问你给定区间的给出的表达式的最小值
思路:看了网上题解,都说中位数便是最优的解,证明不会,那么跟着思路做就行了,用划分树找出中位数然后在划分树中加入区间的前缀和即可,加的时候只要查询的值走到右子树那么就加起来,因为它向右走了,那么左边全是比它小的要加起来,小于它的个数一样统计一下即可,然后有了这两个推个小公式就行了
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <iostream> #include <algorithm> using namespace std; typedef __int64 ll; const int maxn=100010; int t[20][maxn],sum[20][maxn],as[maxn]; ll cnt[maxn],Lcnt[20][maxn],Lcnt1,Lsum; void buildtree(int le,int ri,int p){ int mid=(le+ri)>>1; int lm=mid-le+1,ls=le,rs=mid+1; for(int i=le;i<=ri;i++){ if(t[p][i]<as[mid]) lm--; } for(int i=le;i<=ri;i++){ if(i==le){ sum[p][i]=0; Lcnt[p][i]=0;//初始化 } else{ sum[p][i]=sum[p][i-1]; Lcnt[p][i]=Lcnt[p][i-1];//初始化 } if(t[p][i]==as[mid]){ if(lm){ lm--; sum[p][i]++;Lcnt[p][i]=Lcnt[p][i]+(ll)t[p][i];//加到左子树上,值加起来 t[p+1][ls++]=t[p][i]; }else{ t[p+1][rs++]=t[p][i]; } }else if(t[p][i]<as[mid]){ sum[p][i]++;Lcnt[p][i]=Lcnt[p][i]+(ll)t[p][i];//一样还是加到左子树 t[p+1][ls++]=t[p][i]; }else{ t[p+1][rs++]=t[p][i]; } } if(le==ri) return ; buildtree(le,mid,p+1); buildtree(mid+1,ri,p+1); } ll query(int l,int r,int k,int le,int ri,int node){ int mid=(le+ri)>>1; if(le==ri) return t[node][le]; int s,ss; ll tmp=0; if(l==le) s=0,ss=sum[node][r],tmp=Lcnt[node][r]; else s=sum[node][l-1],ss=sum[node][r]-s,tmp=Lcnt[node][r]-Lcnt[node][l-1]; if(k<=ss) return query(le+s,le+sum[node][r]-1,k,le,mid,node+1); else{//去右区间查询,左边全是小的加起来 Lcnt1+=ss;Lsum+=tmp; return query(mid+1-le+l-s,mid+1-le+r-sum[node][r],k-ss,mid+1,ri,node+1); } } int main(){ int n,m,a,b,T,cas=1; scanf("%d",&T); while(T--){ scanf("%d",&n);cnt[0]=0; for(int i=1;i<=n;i++){ scanf("%d",&as[i]); t[0][i]=as[i]; cnt[i]=cnt[i-1]+as[i]; } sort(as+1,as+1+n); buildtree(1,n,0); scanf("%d",&m); printf("Case #%d:\n",cas++); while(m--){ scanf("%d%d",&a,&b); a++;b++; Lcnt1=0;Lsum=0; ll k=query(a,b,(b-a+2)/2,1,n,0); ll rcnt=b-a-Lcnt1; ll rsum=cnt[b]-cnt[a-1]-k-Lsum; ll tt1=k*Lcnt1-Lsum; ll tt2=rsum-k*rcnt; ll ans=tt1+tt2;//那个小公式很好推 printf("%I64d\n",ans); } printf("\n"); } return 0; }