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; }

 

 

speech.gif posted on 2019-01-16 08:24 kgxpbqbyt 阅读( ...) 评论( ...) 编辑 收藏
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值