[BZOJ3744]Gty的妹子序列 Solution

本文介绍了一种处理在线逆序对查询的高效算法,通过分块和树状数组维护,实现了快速计算区间逆序对数量。适用于强制在线查询场景,提供了一种O(n√nlogn)的时间复杂度解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:给一个序列,有若干个询问,每次询问一个区间的逆序对个数,强制在线。
考虑分块,求出从第一块到任意块之间的逆序对数,再求出从第一块到任意块之间小于等于一个数的个数。然后的答案就可以很方便的计算出来,对于整块之间的,已经计算完毕,对于散块,有两种方法:

  • 用树状数组维护,O(nnlog⁡n)\mathcal{O}(n\sqrt{n}\log{n})O(nnlogn)
  • 归并排序 O(nn)\mathcal{O}(n\sqrt{n})O(nn)

这里我采用了树状数组做法。
code:code:code:

#include <bits/stdc++.h>
#define regi register int
int n,m,blk,num;
int a[51000];
int tmp[51000];
int left[51000];
int right[51000];
int pos[51000];
int color[250][51000];
int pcolor[250][51000];
int lcolor[51000];
int lans[250][250];
int ans,lastans;
std::map<int,int>Dis;
inline void add(int x,int v){
	for(;x<=n;x+=x&-x)
	  lcolor[x]+=v;
}
inline int ask(int x){
	int S=0;
	for(;x;x-=x&-x)
	  S+=lcolor[x];
	return S;
}
main(){
	scanf("%d",&n);
	for(regi i=1;i<=n;++i){
	  scanf("%d",&a[i]);
	  tmp[i]=a[i];
	}
	std::sort(tmp+1,tmp+n+1);
	tmp[0]=-0x3f3f3f3f;
	for(regi i=1;i<=n;++i)
	  if(tmp[i]!=tmp[i-1])
	    Dis[tmp[i]]=Dis[tmp[i-1]]+1;
	for(regi i=1;i<=n;++i)
	  a[i]=Dis[a[i]];
	blk=sqrt(n);
	num=(n+blk-1)/blk;
	for(regi i=1;i<=num;++i){
		left[i]=(i-1)*blk+1;
		right[i]=std::min(i*blk,n);
		for(regi j=left[i];j<=right[i];++j){
		  pos[j]=i;
		  color[i][a[j]]++;
		}
		for(regi j=1;j<=n;++j)
		  color[i][j]+=color[i][j-1];
	}
	for(regi i=1;i<=n;++i)
	  pcolor[1][i]=color[1][i];
	for(regi i=2;i<=num;++i){
	  for(regi j=1;j<=n;++j)
	    pcolor[i][j]=pcolor[i-1][j]+color[i][j];
	}
  for(regi i=1;i<=num;++i){
    for(regi j=i;j<=num;++j){
    	lans[i][j]=lans[i][j-1];
    	for(regi k=left[j];k<=right[j];++k){
    	  lans[i][j]+=k-left[i]-ask(a[k]);
    	  add(a[k],1);
    	}
    }
  	memset(lcolor,0,sizeof lcolor);
 	}
  scanf("%d",&m);
  while(m--){
  	regi L,R;
  	scanf("%d%d",&L,&R);
  	L^=lastans;
  	R^=lastans;
  	ans=0;
  	if(pos[L]==pos[R]){
  		for(regi i=R;i>=L;--i){
  		  ans+=ask(a[i]-1);
  		  add(a[i],1);
  		}
  		for(regi i=L;i<=R;++i)
  		  add(a[i],-1);
  		printf("%d\n",ans);
  	}
  	else{
  		for(regi i=right[pos[L]];i>=L;--i){
  			ans+=ask(a[i]-1)+pcolor[pos[R]-1][a[i]-1]-pcolor[pos[L]][a[i]-1];
  			add(a[i],1);
  		}
    	for(regi i=left[pos[R]];i<=R;++i){
  			ans+=right[pos[L]]-L+1+i-left[pos[R]]-ask(a[i])+(right[pos[R]-1]-left[pos[L]+1]+1-(pcolor[pos[R]-1][a[i]]-pcolor[pos[L]][a[i]]));
  			add(a[i],1);
  		}
  		ans+=lans[pos[L]+1][pos[R]-1];
  		printf("%d\n",ans);
  		for(regi i=L;i<=right[pos[L]];++i)
  		  add(a[i],-1);
  		for(regi i=left[pos[R]];i<=R;++i)
  		  add(a[i],-1);
  	}
  	lastans=ans;
  }
  return 0;
}
/*
如何处理任意两块的逆序对?树状数组维护。
如何处理第一个块到任意块颜色小于等于某一值的数的个数呢?
首先处理出每一块的,O(n*sqrt(n))
然后做前缀和O(n*sqrt(n))
考虑如何产生答案?
答案显然只需要额外算两个零散块产生的影响,时间复杂度正确。
前面那个块的每一个数考虑这个数后面位置以及后面的所有整块的答案,不考虑和最后一个散块产生的答案
最后的那个散块再把前面漏掉的答案算出来就行了。 
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值