Codeforces 785E

#Codeforces 785E(Round404 div2)
题意是初始有一个1,2,3,4....n的序列A,现在有q个操作,每次操作给出两个数L和R,表示交换A[L]和A[R],每次操作后输出当前逆序数的数目。  
逆序数的定义是对于两个数A[i],A[j],有A[i]>A[j]且i<j。  
那么采取分块法,将n个数,每2048个分成一组,对于每一组内,用树状数组维护。  
当交换时,显然,只有在区间[L+1,R-1]内的数的逆序数才会发生改变。  
设X=max(A[L],A[R]),Y=min(A[L],A[R]);  
那么显然在区间[L+1,R-1]内的数,只有大小在[X+1,Y-1]的范围内的数,逆序数才会发生改变。  
为什么?  

因为当A[j]>X或者A[j]<Y时, A[L]和A[R]对A[j]来说是没有区别的,所以即便他们两交换位置也不会的A[j]的逆序数产生影响。  
那么我们唯一需要处理的就是位置区间[L+1,R-1]且值区间[Y+1,X-1]的数的逆序数的改变。  
先处理和A[L-1]同块的数,遍历即可。
再处理和A[R-1]同块的数,遍历即可。
接着处块编号在[blocks(A[L+1])+1,blocks(A[R-1])-1]的块的逆序数。因为每个块由树状数组维护了,sum(X-1)-sum(Y)便能统计出这个块中大于Y小于X的数的个数。  
代码参考[http://codeforces.com/profile/liu_cheng_ao](http://codeforces.com/profile/liu_cheng_ao "lca")的AC代码。  


#include<bits/stdc++.h>
using namespace std;
#define debug puts("Infinity is awesome!")
#define mm(a,b) memset(a,b,sizeof(a))
#define LS (root<<1)
#define RS (root<<1|1)
#define LSON LS,l,mid
#define RSON RS,mid+1,r
#define LL long long
#define rep(a,b,c) for(int a=b;a<c;a++)
const int Inf=1e9+7;
const int maxn=2e5+5;
const int maxb=(maxn>>11);
const int mod=1e9+7;
int blocks(int x){return x>>11;}
int cnt[maxb+5][maxn];
int a[maxn];
int lowbit(int x){
    return x&(-x);
}
void add(int blc,int x,int v){
    while(x<maxn){
        cnt[blc][x]+=v;
        x+=lowbit(x);
    }
}
int sum(int blc,int x){
    int ret=0;
    while(x>0){
        ret+=cnt[blc][x];
        x-=lowbit(x);
    }
    return ret;
}
int main(){
    LL ans=0;
    int n, q;
    int l, r;

    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++){
        a[i]=i;
        add(blocks(i),a[i],1);
    }
    for(int i=0;i<q;i++){
        scanf("%d%d",&l, &r);
        if(l==r) {printf("%lld\n",ans); continue; }
        if(l>r) swap(l,r);
        if(a[l]<a[r]){
            ans++;//a[l]<a[r] -> a[r]>a[l], inversions++
            if(blocks(l+1)==blocks(r-1)){
                for(int j=l+1;j<r;j++)
                if(a[j]>a[l]&&a[j]<a[r]) ans+=2;
            }
            else{
                for(int j=l+1;blocks(j)==blocks(l+1);j++)
                if(a[j]>a[l]&&a[j]<a[r]) ans+=2;

                for(int j=r-1;blocks(j)==blocks(r-1);j--)
                if(a[j]>a[l]&&a[j]<a[r]) ans+=2;

                for(int j=blocks(l+1)+1;j<blocks(r-1);j++)
                ans+=2*((sum(j,a[r]-1))-sum(j,a[l]));
            }
        }else{
            ans--;//a[l]>a[r] -> a[r]<a[l], inversions--
            if(blocks(l+1)==blocks(r-1)){
                for(int j=l+1;j<r;j++)
                if(a[j]<a[l]&&a[j]>a[r]) ans-=2;
            }
            else{
                for(int j=l+1;blocks(j)==blocks(l+1);j++)
                if(a[j]<a[l]&&a[j]>a[r]) ans-=2;

                for(int j=r-1;blocks(j)==blocks(r-1);j--)
                if(a[j]<a[l]&&a[j]>a[r]) ans-=2;

                for(int j=blocks(l+1)+1;j<blocks(r-1);j++)
                ans-=2*(sum(j,a[l]-1)-sum(j,a[r]));
            }
        }
        add(blocks(l),a[l],-1);
        add(blocks(r),a[r],-1);
        swap(a[l],a[r]);
        add(blocks(l),a[l],1);
        add(blocks(r),a[r],1);
        printf("%lld\n",ans);
    }

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值