莫队二次离线学习小记

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)的数的个数。
那么我们要维护支持如下操作的数据结构:

  1. 将某个数(v)加入可重集中,这个操作有(O(n))次。
  2. 询问可重集中多少个数大于(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;
}
原文地址:https://www.cnblogs.com/jz-597/p/13598510.html