bzoj 3744【Gty的妹子序列】
Description
给定一个长度为 n 的序列,第 i 个位置上的数为 ai。现在有 m 组询问, 每组询问给定两个参数 L,R,要求输出从 i∈ [L,R] 中随机抽取两个数, 满足这两个数权值相同的概率。
n,m≤ 5×10^4,ai ≤n
分析
这道题多组询问,求任意区间逆序对数,最暴力写法就是开n个树状数组,查询的时候利用前缀和的思路求一下逆序对数。但是这样好像还不如直接对于每次询问建立一个树状数组直接求来的好。(反正都是暴力,你怎么舒服怎么写呗)。这种题离线的话应该可以写,但它强制在线。考虑分块能否维护信息?
如果一道题可以写分块,套路是一样的,你考虑每一块要维护什么,边缘部分对答案的贡献怎么处理。对于这个题来说,显然你每一个块要维护的是逆序对数,边缘部分对答案的贡献显然是左边的与块内的数形成的逆序对数+左边的与右边的数形成的逆序对数+块内的数与右边的数形成的逆序对数。至于逆序对数怎么求,显然树状数组求,但对于任意边界划分的块都维护一个树状数组,显然不管是空间还是时间都说不过去。这时候就利用到了前缀和的思路,我们只开T个关于后缀的树状数组,求一个数在某个连续块内有多少个小于它的数的时候可以两个树状数组减一下。那么此时查询复杂度是m*s*log n,如果预处理每个块答案复杂度是暴力n^2/s^2*n log n的话,不管怎么调,都超出了1e8. 其实仔细想一下,我们处理每一个后缀的树状数组的时候可以求出T个块的答案。那么预处理复杂度就是n/s*n logn。调和一下,取s=sqrt(n),总复杂度是n sqrt(n)log n,足以通过此题。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<queue> using namespace std; const int N=5e4+10; int lan,t,n,m,p,q,s,a[N],b[300],c[300][N],sum[90000],d[N],c1[N]; void discrete(){ sort(d+1,d+n+1); int sz=unique(d+1,d+n+1)-d-1; for(int i=1;i<=n;++i) a[i]=lower_bound(d+1,d+sz+1,a[i])-d; } void add(int id,int x){ for(;x<=n;x+=x&(-x)) c[id][x]+=1; } int ask(int id,int x){ int res=0; for(;x>0;x-=x&(-x)) res+=c[id][x]; return res; } void add1(int x,int val){ for(;x<=n;x+=x&(-x)) c1[x]+=val; } int ask1(int x){ int res=0; for(;x>0;x-=x&(-x)) res+=c1[x]; return res; } int work(int l,int r){ l=l^lan,r=r^lan; if(l>r) swap(l,r); p=l/s,q=r/s; if(l%s==1) p++; else if(l%s) p+=2; else p++; if(q-p<0){ int ans=0; for(int i=l;i<=r;++i){ ans+=ask1(n)-ask1(a[i]); add1(a[i],1); } for(int i=l;i<=r;++i) add1(a[i],-1); return ans; }else{ int pos=b[p-1]+q-p+1; int ans=sum[pos]; for(int i=l;i<=s*(p-1);++i){ ans+=ask1(n)-ask1(a[i]); add1(a[i],1); } for(int i=q*s+1;i<=r;++i){ ans+=ask1(n)-ask1(a[i]); add1(a[i],1); } for(int i=l;i<=s*(p-1);++i){ add1(a[i],-1); } for(int i=q*s+1;i<=r;++i){ add1(a[i],-1); } for(int i=l;i<=s*(p-1);++i){ ans+=ask(p,a[i]-1)-ask(q+1,a[i]-1); } for(int i=q*s+1;i<=r;++i){ ans+=ask(p,n)-ask(p,a[i])-ask(q+1,n)+ask(q+1,a[i]); } return ans; } } int main(){ //freopen("h.in","r",stdin); //freopen("h.out","w",stdout); scanf("%d",&n); t=sqrt(1.0*n);s=n/t; for(int i=1;i<=n;++i) scanf("%d",&a[i]),d[i]=a[i]; for(int i=1;i<=t;++i) b[i]=b[i-1]+t-i+1; discrete(); for(int i=1;i<=t;++i){ int ans=0; for(int j=(i-1)*s+1;j<=n/s*s;++j){ //这个小错误搞了我一个晚上,这时候应该是分的块数乘上长度,如果直接写n,它多余的不足以构成一整块的还会影响答案
int pos=b[i-1]+j/s-i+1; //这个写的时候也得注意一下 if(j%s) pos++; ans+=ask(i,n)-ask(i,a[j]); sum[pos]=ans; add(i,a[j]); } } scanf("%d",&m); for(int i=1;i<=m;++i){ int l,r;scanf("%d%d",&l,&r); printf("%d\n",lan=work(l,r)); } return 0; }