NOI的时候遇到了个毒瘤的莫队相关。
莫队二次离线可以做其中的部分分。
例题:
给出一个序列 a i {a_i} ai,然后有若干个询问,每次询问 [ l , r ] [l,r] [l,r]中的逆序对个数。
(正好就是NOI2020D1T3的其中一档部分分。)
序列长度为 n n n,询问个数为 m m m。 n n n和 m m m同阶,所以后面分析时间复杂度的时候都用 n n n来表示。
朴素做法:
显然可以莫队,设当前的指针移到 [ L , R ] [L,R] [L,R],用个树状数组装下 [ L , R ] [L,R] [L,R]中的数,在左右指针移动的时候计算答案的变化量。
时间复杂度为 O ( n n lg n ) O(n\sqrt n \lg n) O(nnlgn)
更优秀的做法:可以做到 O ( n n ) O(n \sqrt n) O(nn)。
假如我们要将 [ L , R ] [L,R] [L,R]移动到 [ L , R + 1 ] [L,R+1] [L,R+1],这时候需要询问 [ L , R ] [L,R] [L,R]中大于 a [ R + 1 ] a[R+1] a[R+1]的数的个数。
我们把这个东西看成另一个问题:若干组询问,每次询问 [ l , r ] [l,r] [l,r]中大于(或小于,后面省略不写) v v v的数的个数。这种询问一共有 O ( n n ) O(n\sqrt n) O(nn)个。离线处理这些询问。
这就是莫队二次离线的核心思想。
接下来考虑如何快速计算这个东西。
首先可以差分,变成询问 [ 1 , x ] [1,x] [1,x]中大于 v v v的数的个数。
那么我们要维护支持如下操作的数据结构:
- 将某个数 v v v加入可重集中,这个操作有 O ( n ) O(n) O(n)次。
- 询问可重集中多少个数大于 v v v,这个操作有 O ( n n ) O(n\sqrt n) O(nn)次。
注意到插入和询问次数的复杂度不同,所以可以尝试一下平衡规划:维护一个 O ( n ) O(\sqrt n) O(n)修改、 O ( 1 ) O(1) O(1)查询的数据结构。简单分块即可。
于是总的时间复杂度为 O ( n n ) O(n\sqrt n) O(nn)。
不过,如果暴力存下每个询问,空间复杂度是 O ( n n ) O(n\sqrt n) O(nn)的,可能会承受不住。
尝试去优化。
假设现在的区间在 [ L , R ] [L,R] [L,R],要变成 [ L , R ′ ] [L,R'] [L,R′](设 R < R ′ R<R' R<R′)。(其它的情况类似)
为了方便后面叙述定义符号: [ l , r ] ∣ [ L , R ] [l,r]|[L,R] [l,r]∣[L,R]表示 ∑ x ∈ [ l , r ] , y ∈ [ L , R ] [ a x > a y ] \sum_{x\in [l,r],y \in [L,R]} [a_x>a_y] ∑x∈[l,r],y∈[L,R][ax>ay]。
贡献为 ∑ i = R + 1 R ′ [ L , i − 1 ] ∣ [ i , i ] = ∑ i = R + 1 R ′ [ 1 , i − 1 ] [ i , i ] − [ 1 , L − 1 ] [ R + 1 , R ′ ] \sum_{i=R+1}^{R'}[L,i-1]|[i,i]=\sum_{i=R+1}^{R'}[1,i-1][i,i]-[1,L-1][R+1,R'] ∑i=R+1R′[L,i−1]∣[i,i]=∑i=R+1R′[1,i−1][i,i]−[1,L−1][R+1,R′]
那可以把这个询问挂在 L − 1 L-1 L−1上,记下 [ R + 1 , R ′ ] [R+1,R'] [R+1,R′]等信息。扫到 L − 1 L-1 L−1的时候处理,左边的可以预处理直接得到,右边的用上述数据结构维护。
于是空间复杂度就 O ( n ) O(n) O(n)了。
代码
洛谷P5047
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define N 100010
#define SQ 410
#define ll long long
int n,m,k;
int a[N],*p[N];
bool cmpp(int *x,int *y){
return *x<*y;}
int B,bel[N],nb,L[SQ];
struct Query{
int l,r,t;
} q[N];
bool cmpq(Query x,Query y){
return bel[x.l]<bel[y.l] || bel[x.l]==bel[y.l] && x.r<y.r;}
namespace TA{
int t[N],all;
void clear(){
memset(t,0,sizeof(int)*(k+1));all=0;}
void add(int x,int c){
all+=c;for (;x<=k;x+=x&-x) t[x]+=c;}
int query(int x){
int r=0;for (;x;x-=x&-x) r+=t[x];return r;}
};
struct List{
struct EDGE{
int l,r,o,t;
EDGE *las;
} e[N];
int ne;
EDGE *last[N];
void insert(int x,int l,int r,int o,int t){
e[ne]={
l,r,o,t,last[x]};
last[x]=e+ne++;
}
} q0,q1;
#define re(x) (n+1-(x))
int sum0[N],sum1[SQ];
void insert(int x){
for (int i=x;i>=L[bel[x]];--i)
sum0[i]++;
for (int i=bel[x]-1;i>=1;--i)
sum1[i]++;
}
ll ans2[N],ans[N];
void work2(List &q){
static int f[N];
TA::clear();
for (int i=1;i<=n;++i){
f[i]=TA::all-TA::query(a[i]);
TA::add(a[i],1);
}
memset(sum0,0,sizeof(int)*(k+1));
memset(sum1,0,sizeof(int)*(nb+1));
for (int i=0;i<=n;++i){
if (i)
insert(a[i]-1);
for (List::EDGE *ei=q.last[i];ei;ei=ei->las){
ll s=0;
int l=ei->l,r=ei->r;
for (int x=l;x<=r;++x)
s+=f[x]-(sum0[a[x]]+sum1[bel[a[x]]]);
ans2[ei->t]+=ei->o*s;
}
}
}
void work(){
sort(q+1,q+m+1,cmpq);
int l=1,r=0;
for (int i=1;i<=m;++i){
int L=q[i].l,R=q[i].r;
if (r<R) q0.insert(l-1,r+1,R,1,i),r=R;
if (l>L) q1.insert(re(r+1),re(l-1),re(L),1,i),l=L;
if (r>R) q0.insert(l-1,R+1,r,-1,i),r=R;
if (l<L) q1.insert(re(r+1),re(L-1),re(l),-1,i),l=L;
}
work2(q0);
reverse(a+1,a+n+1);
for (int i=1;i<=n;++i)
a[i]=k+1-a[i];
work2(q1);
for (int i=1;i<=m;++i)
ans[q[i].t]=ans2[i]+=ans2[i-1];
}
int main(){
scanf("%d%d",&n,&m);
for (int i=1;i<=n;++i)
scanf("%d",&a[i]),p[i]=&a[i];
sort(p+1,p+n+1,cmpp);
for (int i=1,last=-1;i<=n;++i){
if (*p[i]!=last)
last=*p[i],++k;
*p[i]=k;
}
B=sqrt(n);
for (int i=1;i<=n;++i)
nb=bel[i]=(i-1)/B+1;
for (int i=n;i>=1;--i)
L[bel[i]]=i;
for (int i=1;i<=m;++i)
scanf("%d%d",&q[i].l,&q[i].r),q[i].t=i;
work();
for (int i=1;i<=m;++i)
printf("%lld\n",ans[i]);
return 0;
}