NoiminのNoise

競技プログラミング (多め) とWeb (たまに) ,自然言語処理 (ブログではまだ)。数式の書き方を一気に KaTeX に変えようとして記事を全削除してインポートし直すなどしたので,過去にブックマークされた記事は URL が変わってしまっている可能性があります…….

AtCoder Regular Contest 097 E - Sorted and Sorted

E - Sorted and Sorted

問題概要

1からNまでの数字が書かれた,黒・白のボールがある. この2N個のボールを,それぞれの色について数字が昇順になるように並べ替える. 隣同士のボールの交換を繰り返して並べ替えを行うとき,最小の交換回数を求める.

解法概要

「隣同士の交換」「交換回数」とか言っているので,BITによる転倒数の計算が使えそうという気持ちになる.

ただし,ボールは2色ある.問題に書いてある並べ替えの条件は数字についてだけで,黒・白の2色がどのように並ぶべきかはわからない.

そこで,次のようなDPテーブルを定義する

dp[i][j] ($ 0 \leq i,j \leq n $) : 黒いボールは数字iまで,白いボールは数字jまでのボールが問題の条件を満たす順に並ぶために必要な最小の交換回数

dp[i][j]から遷移できるのはdp[i+1][j],dp[i][j+1]のみであることを利用しつつ,BITで転倒数を求める過程でDPテーブルを埋めていく.

ソースコード

#include<iostream>
#include<utility>
#include<vector>
#include<climits>

using namespace std;

typedef pair<int, int> Pii;

class BIT {
    public:
    int n;
    int bit[1000001];
    
    BIT(int n){
        this->n = n;
        fill(bit, bit+n, 0);
    }

    void add(int idx, int x){
        for(int i=idx;i<=this->n;i+=i&-i) bit[i] += x;
    }

    //bit[1]からbit[end]までの和 (閉区間)
    int sum(int end){
        int ret = 0;
        for(int i=end;i>=1;i-=i&-i) ret += bit[i];
        return ret;
    }
};


int main() {
    std::ios::sync_with_stdio(0); cin.tie(0);
    int n;
    cin >> n;

    int mp[2][n+1];
    char c;
    int num;
    for(int i=1;i<=2*n;++i){
        cin >> c >> num;
        mp[(c=='W'?0:1)][num] = i;
    }

    vector< vector<int> > dp(n+1, vector<int>(n+1, INT_MAX));
    dp[0][0] = 0;
    BIT tree = BIT(2*n+1);

    for(int i=0;i<=n;++i){
        if(i) tree.add(mp[0][i], 1);

        for(int j=0;j<=n;++j){
            if(j) tree.add(mp[1][j], 1);
            if(i != n) dp[i+1][j] = min(dp[i+1][j], dp[i][j] + (i+j-tree.sum(mp[0][i+1])));
            if(j != n) dp[i][j+1] = min(dp[i][j+1], dp[i][j] + (i+j-tree.sum(mp[1][j+1])));
        }

        for(int j=1;j<=n;++j) tree.add(mp[1][j], -1);
    }

    cout << dp[n][n] << endl;
}

感想

BIT使えば良さそうまでは本番でも思いついたけど,DPとはまいったな〜. でも書いてみたらすごく綺麗に解けて感動した. これからは「順番を固定さえできれば解けるのにな〜」という気持ちになったらDPを考えてみることにしよう.

ちなみに貰うDPで書くよりも配るDPで書くほうが綺麗に書けた (主にBITのクエリ関連).