転倒数計算

問題

  • 問題リンク:転倒数
  • 転倒数とは,配列の中で,ある要素の前にある要素の値が大きいものの個数のこと
    • 厳密には,長さ$N$の数列$A_1,A_2,A_3,\cdots,A_N$で,$i<j$で$A_i>A_j$となる$(i,j)$の個数
  • あるいは,バブルソートを行ったときに,要素の交換回数のこと

解法

実際にバブルソートをすると要素数$N$で$O(N^2)$かかってしまうが,転倒数を計算するだけなら$1$点加算と区間和の取得ができるデータ構造BITやセグ木を用いて$O(N\log N)$で計算できる.

template<typename T>
struct RSQ{
    int n;
    vector<T>dat;
    const T ZERO;
    RSQ(int n_,T ZERO_):ZERO(ZERO_){
        n=1;
        while(n<n_)n*=2;
        //完全二分木にする
        dat.assign(2*n-1,ZERO);
    }
    void update(int k,T a){
        k+=n-1;// i番目は、配列上では n-1+i 番目に格納されている
        dat[k]+=a;// 葉の更新
        while(k>0){
            k=(k-1)/2; //親のインデックス
            // 子の和を計算
            dat[k]=dat[k*2+1]+dat[k*2+2];
        }
    }
    
    T query(int a,int b,int k,int l,int r){
        if(r<=a||b<=l)return ZERO;//範囲外
        if(a<=l&&r<=b)return dat[k]; //範囲内である
        else{
            T vl=query(a,b,k*2+1,l,(l+r)/2);
            T vr=query(a,b,k*2+2,(l+r)/2,r);
            return vl+vr;
        }
    }
    //[a,b)の総和を求める
    T query(int a,int b){
        return query(a,b,0,0,n);
    }
};


int main(void){
    /*転倒数計算*/
    int n;cin>>n;
    vector<int>A(n);
    for(int i=0;i<n;i++){
        cin>>A[i];
        A[i]--;
    }
    RSQ<int>rsq(n,0);
    long ans=0;
    for(int i=0;i<n;i++){
        ans+=rsq.query(A[i]+1,n);
        rsq.update(A[i],1);
    }
    cout<<ans<<endl;
}

提出リンク