莫队二次离线学习小记

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(nn lgn)


更优秀的做法:可以做到 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的数的个数。
那么我们要维护支持如下操作的数据结构:

  1. 将某个数 v v v加入可重集中,这个操作有 O ( n ) O(n) O(n)次。
  2. 询问可重集中多少个数大于 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,i1][i,i]=i=R+1R[1,i1][i,i][1,L1][R+1,R]
那可以把这个询问挂在 L − 1 L-1 L1上,记下 [ R + 1 , R ′ ] [R+1,R'] [R+1,R]等信息。扫到 L − 1 L-1 L1的时候处理,左边的可以预处理直接得到,右边的用上述数据结构维护。
于是空间复杂度就 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;
}

猜你喜欢

转载自blog.csdn.net/A1847225889/article/details/108350011
今日推荐