二次离线莫队算法

二次离线莫队算法可以计算区间中满足某种条件的点对的数量,例如P5047

题意:给定一个长度为n的序列 m次查询 求区间[L,R]中逆序对的数量

分析:如果直接用普通莫队做这道题的话,每次移动都要在树状数组上修改和查询,复杂度为 O ( n n l o g n ) O(n\sqrt{n}logn) O(nn logn)

( [ l 1 , r 1 ] , [ l 2 , r 2 ] ) ([l1,r1],[l2,r2]) ([l1,r1],[l2,r2]) 表示一个点在 [ l 1 , r 1 ] [l1,r1] [l1,r1] 中另外一点在 [ l 2 , r 2 ] [l2,r2] [l2,r2] 中的逆序对数

在做莫队的过程中,假如要将区间 [ l , r ] [l,r] [l,r] ,通过右端点右移,变成区间 [ l , r ′ ] [l,r'] [l,r] 。那么一次从 [ l , i − 1 ] [l,i-1] [l,i1] 变到 [ l , i ] [l,i] [l,i] 的移动,要让当前维护的答案加上 ( [ l , i − 1 ] , [ i , i ] ) ([l,i-1],[i,i]) ([l,i1],[i,i]) ,转化一下也就是 ( [ 1 , i − 1 ] , [ i , i ] ) − ( [ 1 , l − 1 ] , [ i , i ] ) ([1,i-1],[i,i])-([1,l-1],[i,i]) ([1,i1],[i,i])([1,l1],[i,i])

因此在所有移动之后,当前答案应该加上: ∑ i = r + 1 r ′ ( [ 1 , i − 1 ] , [ i , i ] ) − ( [ 1 , l − 1 ] , [ r + 1 , r ′ ] ) \sum\limits_{i=r+1}^{r'}{([1,i-1],[i,i])}-([1,l-1],[r+1,r']) i=r+1r([1,i1],[i,i])([1,l1],[r+1,r])

前者只有n种情况,用树状数组 O ( n l o g n ) O(nlogn) O(nlogn) 进行预处理,然后利用前缀和进行计算

后者是一个前缀和一个区间的形式,我们可以离线将每个区间保存在其前缀上,那么可以从左到右处理每一个前缀以及挂在这个前缀上的区间 [ r + 1 , r ′ ] [r+1,r'] [r+1,r]

所有 [ r + 1 , r ′ ] [r+1,r'] [r+1,r] 的总长,由于莫队的性质,是 O ( n n ) O(n\sqrt{n}) O(nn ) 级别的,而前缀的数量是 O ( n ) O(n) O(n) 的,我们可以用一个 O ( n ) O(\sqrt{n}) O(n ) 单点修改 O ( 1 ) O(1) O(1) 查询的分块。每次前缀加入一个新数,就在容器中 O ( n ) O(\sqrt{n}) O(n ) 进行修改。查询一个位置的元素与前缀形成的逆序对数是 O ( 1 ) O(1) O(1)

总复杂度就是 O ( n l o g n + n n ) O(nlogn+n\sqrt{n}) O(nlogn+nn )

对于左端点左移,左端点右移,右端点左移也类似,要注意的是左端点移动要用后缀

const int M=1e5+5;
const int S=sqrt(M);
const int K=M/S+5;
struct BLOCK{
	int bel[M],L[K],R[K],big[K],small[M];
	BLOCK(){
		for(int i=1;i<M;i++)bel[i]=(i-1)/S+1;
		for(int i=1;i<K;i++)L[i]=1+(i-1)*S,R[i]=i*S;
	}
	void clear(){
		memset(big,0,sizeof(big));
		memset(small,0,sizeof(small));
	}
	void update(int x,int v){
		for(int i=1;i<=bel[x];i++)big[i]+=v;
		for(int i=L[bel[x]];i<=x;i++)small[i]+=v;
	}
	int query(int l,int r){
		return big[bel[l]+1]+small[l]-big[bel[r+1]+1]-small[r+1];
	}
}block;
struct node{
	int l,r,id;
	bool operator <(const node &A)const{
		if(l/S!=A.l/S)return l/S<A.l/S;
		if(l/S&1)return r<A.r;
		else return r>A.r;
	}
}Q[M];
int n,m,uni,A[M],tmp[M],bit[M];
ll ans[M],sum1[M],sum2[M],res1[M],res2[M];
vector<node>vi1[M],vi2[M];
void add(int x,int v){
	while(x<=uni)bit[x]+=v,x+=x&-x;
}
int query(int x){
	int res=0;
	while(x)res+=bit[x],x-=x&-x;
	return res;
}
int main(){
#ifndef ONLINE_JUDGE
	freopen("jiedai.in","r",stdin);
#endif
	rd(n),rd(m);
	for(int i=1;i<=n;i++)rd(A[i]),tmp[i]=A[i];
	for(int i=1;i<=m;i++)rd(Q[i].l),rd(Q[i].r),Q[i].id=i;
	sort(tmp+1,tmp+1+n);
	uni=unique(tmp+1,tmp+1+n)-tmp-1;
	for(int i=1;i<=n;i++)A[i]=lower_bound(tmp+1,tmp+1+uni,A[i])-tmp;
	sort(Q+1,Q+1+m);
	for(int i=1,l=1,r=0;i<=m;i++){
		if(Q[i].r>r)vi1[l-1].push_back((node){r+1,Q[i].r,i}),r=Q[i].r;
		if(Q[i].l<l)vi2[r+1].push_back((node){Q[i].l,l-1,i}),l=Q[i].l;
		if(Q[i].r<r)vi1[l-1].push_back((node){Q[i].r+1,r,i}),r=Q[i].r;
		if(Q[i].l>l)vi2[r+1].push_back((node){l,Q[i].l-1,i}),l=Q[i].l;
	}
	memset(bit,0,sizeof(bit));
	for(int i=1;i<=n;i++){
		sum1[i]=sum1[i-1]+i-1-query(A[i]);
		add(A[i],1);
	}
	memset(bit,0,sizeof(bit));
	for(int i=n;i>=1;i--){
		sum2[i]=sum2[i+1]+query(A[i]-1);
		add(A[i],1);
	}
	block.clear();
	for(int i=0;i<n;i++){
		for(int j=0;j<vi1[i].size();j++){
			int l=vi1[i][j].l,r=vi1[i][j].r,id=vi1[i][j].id;
			for(int k=l;k<=r;k++)res1[id]+=block.query(A[k]+1,uni);
		}
		block.update(A[i+1],1);
	}
	block.clear();
	for(int i=n+1;i>1;i--){
		for(int j=0;j<vi2[i].size();j++){
			int l=vi2[i][j].l,r=vi2[i][j].r,id=vi2[i][j].id;
			for(int k=l;k<=r;k++)res2[id]+=block.query(1,A[k]-1);
		}
		block.update(A[i-1],1);
	}
	ll res=0;
	for(int i=1,l=1,r=0;i<=m;i++){
		if(Q[i].r>r)res+=sum1[Q[i].r]-sum1[r]-res1[i],r=Q[i].r;
		if(Q[i].l<l)res+=sum2[Q[i].l]-sum2[l]-res2[i],l=Q[i].l;
		if(Q[i].r<r)res-=sum1[r]-sum1[Q[i].r]-res1[i],r=Q[i].r;
		if(Q[i].l>l)res-=sum2[l]-sum2[Q[i].l]-res2[i],l=Q[i].l;
		ans[Q[i].id]=res;
	}
	for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
	return (0-0);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值