二次离线莫队算法可以计算区间中满足某种条件的点对的数量,例如P5047
题意:给定一个长度为n的序列 m次查询 求区间[L,R]中逆序对的数量
分析:如果直接用普通莫队做这道题的话,每次移动都要在树状数组上修改和查询,复杂度为 O ( n n l o g n ) O(n\sqrt{n}logn) O(nnlogn)
记 ( [ l 1 , r 1 ] , [ l 2 , r 2 ] ) ([l1,r1],[l2,r2]) ([l1,r1],[l2,r2]) 表示一个点在 [ l 1 , r 1 ] [l1,r1] [l1,r1] 中另外一点在 [ l 2 , r 2 ] [l2,r2] [l2,r2] 中的逆序对数
在做莫队的过程中,假如要将区间 [ l , r ] [l,r] [l,r] ,通过右端点右移,变成区间 [ l , r ′ ] [l,r'] [l,r′] 。那么一次从 [ l , i − 1 ] [l,i-1] [l,i−1] 变到 [ l , i ] [l,i] [l,i] 的移动,要让当前维护的答案加上 ( [ l , i − 1 ] , [ i , i ] ) ([l,i-1],[i,i]) ([l,i−1],[i,i]) ,转化一下也就是 ( [ 1 , i − 1 ] , [ i , i ] ) − ( [ 1 , l − 1 ] , [ i , i ] ) ([1,i-1],[i,i])-([1,l-1],[i,i]) ([1,i−1],[i,i])−([1,l−1],[i,i])
因此在所有移动之后,当前答案应该加上: ∑ i = r + 1 r ′ ( [ 1 , i − 1 ] , [ i , i ] ) − ( [ 1 , l − 1 ] , [ r + 1 , r ′ ] ) \sum\limits_{i=r+1}^{r'}{([1,i-1],[i,i])}-([1,l-1],[r+1,r']) i=r+1∑r′([1,i−1],[i,i])−([1,l−1],[r+1,r′])
前者只有n种情况,用树状数组 O ( n l o g n ) O(nlogn) O(nlogn) 进行预处理,然后利用前缀和进行计算
后者是一个前缀和一个区间的形式,我们可以离线将每个区间保存在其前缀上,那么可以从左到右处理每一个前缀以及挂在这个前缀上的区间 [ r + 1 , r ′ ] [r+1,r'] [r+1,r′]
所有 [ r + 1 , r ′ ] [r+1,r'] [r+1,r′] 的总长,由于莫队的性质,是 O ( n n ) O(n\sqrt{n}) O(nn) 级别的,而前缀的数量是 O ( n ) O(n) O(n) 的,我们可以用一个 O ( n ) O(\sqrt{n}) O(n) 单点修改 O ( 1 ) O(1) O(1) 查询的分块。每次前缀加入一个新数,就在容器中 O ( n ) O(\sqrt{n}) O(n) 进行修改。查询一个位置的元素与前缀形成的逆序对数是 O ( 1 ) O(1) O(1) 的
总复杂度就是 O ( n l o g n + n n ) O(nlogn+n\sqrt{n}) O(nlogn+nn)
对于左端点左移,左端点右移,右端点左移也类似,要注意的是左端点移动要用后缀
const int M=1e5+5;
const int S=sqrt(M);
const int K=M/S+5;
struct BLOCK{
int bel[M],L[K],R[K],big[K],small[M];
BLOCK(){
for(int i=1;i<M;i++)bel[i]=(i-1)/S+1;
for(int i=1;i<K;i++)L[i]=1+(i-1)*S,R[i]=i*S;
}
void clear(){
memset(big,0,sizeof(big));
memset(small,0,sizeof(small));
}
void update(int x,int v){
for(int i=1;i<=bel[x];i++)big[i]+=v;
for(int i=L[bel[x]];i<=x;i++)small[i]+=v;
}
int query(int l,int r){
return big[bel[l]+1]+small[l]-big[bel[r+1]+1]-small[r+1];
}
}block;
struct node{
int l,r,id;
bool operator <(const node &A)const{
if(l/S!=A.l/S)return l/S<A.l/S;
if(l/S&1)return r<A.r;
else return r>A.r;
}
}Q[M];
int n,m,uni,A[M],tmp[M],bit[M];
ll ans[M],sum1[M],sum2[M],res1[M],res2[M];
vector<node>vi1[M],vi2[M];
void add(int x,int v){
while(x<=uni)bit[x]+=v,x+=x&-x;
}
int query(int x){
int res=0;
while(x)res+=bit[x],x-=x&-x;
return res;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("jiedai.in","r",stdin);
#endif
rd(n),rd(m);
for(int i=1;i<=n;i++)rd(A[i]),tmp[i]=A[i];
for(int i=1;i<=m;i++)rd(Q[i].l),rd(Q[i].r),Q[i].id=i;
sort(tmp+1,tmp+1+n);
uni=unique(tmp+1,tmp+1+n)-tmp-1;
for(int i=1;i<=n;i++)A[i]=lower_bound(tmp+1,tmp+1+uni,A[i])-tmp;
sort(Q+1,Q+1+m);
for(int i=1,l=1,r=0;i<=m;i++){
if(Q[i].r>r)vi1[l-1].push_back((node){r+1,Q[i].r,i}),r=Q[i].r;
if(Q[i].l<l)vi2[r+1].push_back((node){Q[i].l,l-1,i}),l=Q[i].l;
if(Q[i].r<r)vi1[l-1].push_back((node){Q[i].r+1,r,i}),r=Q[i].r;
if(Q[i].l>l)vi2[r+1].push_back((node){l,Q[i].l-1,i}),l=Q[i].l;
}
memset(bit,0,sizeof(bit));
for(int i=1;i<=n;i++){
sum1[i]=sum1[i-1]+i-1-query(A[i]);
add(A[i],1);
}
memset(bit,0,sizeof(bit));
for(int i=n;i>=1;i--){
sum2[i]=sum2[i+1]+query(A[i]-1);
add(A[i],1);
}
block.clear();
for(int i=0;i<n;i++){
for(int j=0;j<vi1[i].size();j++){
int l=vi1[i][j].l,r=vi1[i][j].r,id=vi1[i][j].id;
for(int k=l;k<=r;k++)res1[id]+=block.query(A[k]+1,uni);
}
block.update(A[i+1],1);
}
block.clear();
for(int i=n+1;i>1;i--){
for(int j=0;j<vi2[i].size();j++){
int l=vi2[i][j].l,r=vi2[i][j].r,id=vi2[i][j].id;
for(int k=l;k<=r;k++)res2[id]+=block.query(1,A[k]-1);
}
block.update(A[i-1],1);
}
ll res=0;
for(int i=1,l=1,r=0;i<=m;i++){
if(Q[i].r>r)res+=sum1[Q[i].r]-sum1[r]-res1[i],r=Q[i].r;
if(Q[i].l<l)res+=sum2[Q[i].l]-sum2[l]-res2[i],l=Q[i].l;
if(Q[i].r<r)res-=sum1[r]-sum1[Q[i].r]-res1[i],r=Q[i].r;
if(Q[i].l>l)res-=sum2[l]-sum2[Q[i].l]-res2[i],l=Q[i].l;
ans[Q[i].id]=res;
}
for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
return (0-0);
}