题意:给一个序列,有若干个询问,每次询问一个区间的逆序对个数,强制在线。
考虑分块,求出从第一块到任意块之间的逆序对数,再求出从第一块到任意块之间小于等于一个数的个数。然后的答案就可以很方便的计算出来,对于整块之间的,已经计算完毕,对于散块,有两种方法:
- 用树状数组维护,O(nnlogn)\mathcal{O}(n\sqrt{n}\log{n})O(nnlogn)
- 归并排序 O(nn)\mathcal{O}(n\sqrt{n})O(nn)
这里我采用了树状数组做法。
code:code:code:
#include <bits/stdc++.h>
#define regi register int
int n,m,blk,num;
int a[51000];
int tmp[51000];
int left[51000];
int right[51000];
int pos[51000];
int color[250][51000];
int pcolor[250][51000];
int lcolor[51000];
int lans[250][250];
int ans,lastans;
std::map<int,int>Dis;
inline void add(int x,int v){
for(;x<=n;x+=x&-x)
lcolor[x]+=v;
}
inline int ask(int x){
int S=0;
for(;x;x-=x&-x)
S+=lcolor[x];
return S;
}
main(){
scanf("%d",&n);
for(regi i=1;i<=n;++i){
scanf("%d",&a[i]);
tmp[i]=a[i];
}
std::sort(tmp+1,tmp+n+1);
tmp[0]=-0x3f3f3f3f;
for(regi i=1;i<=n;++i)
if(tmp[i]!=tmp[i-1])
Dis[tmp[i]]=Dis[tmp[i-1]]+1;
for(regi i=1;i<=n;++i)
a[i]=Dis[a[i]];
blk=sqrt(n);
num=(n+blk-1)/blk;
for(regi i=1;i<=num;++i){
left[i]=(i-1)*blk+1;
right[i]=std::min(i*blk,n);
for(regi j=left[i];j<=right[i];++j){
pos[j]=i;
color[i][a[j]]++;
}
for(regi j=1;j<=n;++j)
color[i][j]+=color[i][j-1];
}
for(regi i=1;i<=n;++i)
pcolor[1][i]=color[1][i];
for(regi i=2;i<=num;++i){
for(regi j=1;j<=n;++j)
pcolor[i][j]=pcolor[i-1][j]+color[i][j];
}
for(regi i=1;i<=num;++i){
for(regi j=i;j<=num;++j){
lans[i][j]=lans[i][j-1];
for(regi k=left[j];k<=right[j];++k){
lans[i][j]+=k-left[i]-ask(a[k]);
add(a[k],1);
}
}
memset(lcolor,0,sizeof lcolor);
}
scanf("%d",&m);
while(m--){
regi L,R;
scanf("%d%d",&L,&R);
L^=lastans;
R^=lastans;
ans=0;
if(pos[L]==pos[R]){
for(regi i=R;i>=L;--i){
ans+=ask(a[i]-1);
add(a[i],1);
}
for(regi i=L;i<=R;++i)
add(a[i],-1);
printf("%d\n",ans);
}
else{
for(regi i=right[pos[L]];i>=L;--i){
ans+=ask(a[i]-1)+pcolor[pos[R]-1][a[i]-1]-pcolor[pos[L]][a[i]-1];
add(a[i],1);
}
for(regi i=left[pos[R]];i<=R;++i){
ans+=right[pos[L]]-L+1+i-left[pos[R]]-ask(a[i])+(right[pos[R]-1]-left[pos[L]+1]+1-(pcolor[pos[R]-1][a[i]]-pcolor[pos[L]][a[i]]));
add(a[i],1);
}
ans+=lans[pos[L]+1][pos[R]-1];
printf("%d\n",ans);
for(regi i=L;i<=right[pos[L]];++i)
add(a[i],-1);
for(regi i=left[pos[R]];i<=R;++i)
add(a[i],-1);
}
lastans=ans;
}
return 0;
}
/*
如何处理任意两块的逆序对?树状数组维护。
如何处理第一个块到任意块颜色小于等于某一值的数的个数呢?
首先处理出每一块的,O(n*sqrt(n))
然后做前缀和O(n*sqrt(n))
考虑如何产生答案?
答案显然只需要额外算两个零散块产生的影响,时间复杂度正确。
前面那个块的每一个数考虑这个数后面位置以及后面的所有整块的答案,不考虑和最后一个散块产生的答案
最后的那个散块再把前面漏掉的答案算出来就行了。
*/