返回顶部

树状数组求逆序对

  • 之前求逆序对一直用的都是归并排序的方法,后来发现大家都是用树状数组来求解的,因为树状数组不仅写起来简单,还可以动态维护一些东西.

  • 对于一个序列,我们正向遍历他的所有元素,然后每次把当前元素插入到树状数组中,即\(c[a[i]]=1\),然后看区间\(c[a[i]],max(a[i])]\)中有多少个已经被插入的元素

    举个例子:5 6 1 2 3 4

    刚开始的\(c\)数组为:\(0,0,0,0,0,0\),插入\(5\)后为:\(0,0,0,0,1,0\),插入\(6\)后为:\(0,0,0,0,1,1\),插入\(1\)后为\(1,0,0,0,1,1\),此时发现在\(1\)的后面\(5\)\(6\)已经被插入了,即\(c[2,6]\)的后缀和为\(2\),所以此时产生了2个逆序对,对\(2,3,4\)也是同理.

  • 那么我们要怎么去统计这个后缀区间的和呢?其实很简单,我们遍历到第\(i\)的数的时候是不是插入了\(i\)个数?根据树状数组的性质,我们可以求出\([1,a[i]]\)的前缀和,由于一共插入了\(i\)个数,而\([1,a[i]]\)的个数我们可以求出,那么后缀和不就是为:\(i-sum[a[i]]\)吗?

  • \(a[i]\)比较小的时候可以直接用\(c\)记录\(a[i]\),但是\(a[i]\)很大的话,是不能用作下标被\(c\)数组存的,所以此时我们就需要用离散化来进行操作.具体实现方式是:先用一个新数组\(all\)copy一下\(a\).然后对\(all\)排序再用unique函数去重,这样我们就可以将\(a\)数组映射成一些很小的数字,然后遍历的时候可以用二分查找\(a[i]\)\(all\)数组所对应的值,

  • 代码:

    int n;
    ll a[N];
    ll c[N];
    vector<ll> all;
    
    ll lowbit(ll x){
        return x&(-x);
    }
    
    void updata(ll i,int k){
        while(i<=n){
            c[i]+=k;
            i+=lowbit(i);
        }
    }
    
    ll get_sum(ll i){
        ll res=0;
        while(i){
            res+=c[i];
            i-=lowbit(i);
        }
        return res;
    }
    
    int main() {
        ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
        cin>>n;
        for(int i=1;i<=n;++i){
            cin>>a[i];
            all.pb(a[i]);
        }
    
        sort(all.begin(),all.end());
        all.erase(unique(all.begin(),all.end()),all.end());
    
        ll ans=0;
        for(int i=1;i<=n;++i){
            int cur=lower_bound(all.begin(),all.end(),a[i])-all.begin();
            updata(cur+1,1);
            ans+=i-get_sum(cur+1);
        }
    
        cout<<ans<<endl;
    
        return 0;
    }
    
posted @ 2020-11-06 09:52  Rayotaku  阅读(145)  评论(0编辑  收藏  举报