莫队二次离线学习小记

NOI的时候遇到了个毒瘤的莫队相关。
莫队二次离线可以做其中的部分分。


例题:
给出一个序列\({a_i}\),然后有若干个询问,每次询问\([l,r]\)中的逆序对个数。
(正好就是NOI2020D1T3的其中一档部分分。)
序列长度为\(n\),询问个数为\(m\)\(n\)\(m\)同阶,所以后面分析时间复杂度的时候都用\(n\)来表示。

朴素做法:
显然可以莫队,设当前的指针移到\([L,R]\),用个树状数组装下\([L,R]\)中的数,在左右指针移动的时候计算答案的变化量。
时间复杂度为\(O(n\sqrt n \lg n)\)


更优秀的做法:可以做到\(O(n \sqrt n)\)
假如我们要将\([L,R]\)移动到\([L,R+1]\),这时候需要询问\([L,R]\)中大于\(a[R+1]\)的数的个数。
我们把这个东西看成另一个问题:若干组询问,每次询问\([l,r]\)中大于(或小于,后面省略不写)\(v\)的数的个数。这种询问一共有\(O(n\sqrt n)\)个。离线处理这些询问。
这就是莫队二次离线的核心思想。


接下来考虑如何快速计算这个东西。
首先可以差分,变成询问\([1,x]\)中大于\(v\)的数的个数。
那么我们要维护支持如下操作的数据结构:

  1. 将某个数\(v\)加入可重集中,这个操作有\(O(n)\)次。
  2. 询问可重集中多少个数大于\(v\),这个操作有\(O(n\sqrt n)\)次。

注意到插入和询问次数的复杂度不同,所以可以尝试一下平衡规划:维护一个\(O(\sqrt n)\)修改、\(O(1)\)查询的数据结构。简单分块即可。
于是总的时间复杂度为\(O(n\sqrt n)\)


不过,如果暴力存下每个询问,空间复杂度是\(O(n\sqrt n)\)的,可能会承受不住。
尝试去优化。
假设现在的区间在\([L,R]\),要变成\([L,R']\)(设\(R<R'\))。(其它的情况类似)
为了方便后面叙述定义符号:\([l,r]|[L,R]\)表示\(\sum_{x\in [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\n",ans[i]);
	return 0;
}
posted @ 2020-09-01 20:45  jz_597  阅读(231)  评论(0编辑  收藏  举报