NOI的时候遇到了个毒瘤的莫队相关。
莫队二次离线可以做其中的部分分。
例题:
给出一个序列({a_i}),然后有若干个询问,每次询问([l,r])中的逆序对个数。
(正好就是NOI2020D1T3的其中一档部分分。)
序列长度为(n),询问个数为(m)。(n)和(m)同阶,所以后面分析时间复杂度的时候都用(n)来表示。
朴素做法:
显然可以莫队,设当前的指针移到([L,R]),用个树状数组装下([L,R])中的数,在左右指针移动的时候计算答案的变化量。
时间复杂度为(O(nsqrt n lg n))
更优秀的做法:可以做到(O(n sqrt n))。
假如我们要将([L,R])移动到([L,R+1]),这时候需要询问([L,R])中大于(a[R+1])的数的个数。
我们把这个东西看成另一个问题:若干组询问,每次询问([l,r])中大于(或小于,后面省略不写)(v)的数的个数。这种询问一共有(O(nsqrt n))个。离线处理这些询问。
这就是莫队二次离线的核心思想。
接下来考虑如何快速计算这个东西。
首先可以差分,变成询问([1,x])中大于(v)的数的个数。
那么我们要维护支持如下操作的数据结构:
- 将某个数(v)加入可重集中,这个操作有(O(n))次。
- 询问可重集中多少个数大于(v),这个操作有(O(nsqrt n))次。
注意到插入和询问次数的复杂度不同,所以可以尝试一下平衡规划:维护一个(O(sqrt n))修改、(O(1))查询的数据结构。简单分块即可。
于是总的时间复杂度为(O(nsqrt n))。
不过,如果暴力存下每个询问,空间复杂度是(O(nsqrt n))的,可能会承受不住。
尝试去优化。
假设现在的区间在([L,R]),要变成([L,R'])(设(R<R'))。(其它的情况类似)
为了方便后面叙述定义符号:([l,r]|[L,R])表示(sum_{xin [l,r],y in [L,R]} [a_x>a_y])。
贡献为(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'])
那可以把这个询问挂在(L-1)上,记下([R+1,R'])等信息。扫到(L-1)的时候处理,左边的可以预处理直接得到,右边的用上述数据结构维护。
于是空间复杂度就(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
",ans[i]);
return 0;
}