SRM 663 Div2 Hard CheeseRolling

TopCoder Statistics - Problem Statement

  • 復習。解き方がさっぱりわからず SRM 663 div2 hard:CheeseRolling - mayoko’s diary を見て解いた。kmjp 先生然り、日本語の解説ページは色々助かる。
  • 解法理解するのに時間がかかった。
  • それにしても、みんなよくこんなの解けるな...。
  • 下のコードの comb あたりのコードは蟻本にのっていたやつ。(どうしてこれでうまく行くのかは正直知らない。)
  • ビットによるグループ分割だと同じグループで場所を入れ替えたケースとか計算されてなくね?って思ったがそんなことはなかった。(下コメントの "S の 1bit は S1 にも.." ってあたりの話)
class CheeseRolling {
public:
vector<long long> waysToWin(vector <string> wins) {
        int N = wins.size();
        //
        // 各 i に対して i がトーナメントで優勝できるようなトーナメントの組み合わせを求める
        // 全探索すると n! 通りになる
        //
        // そこで以下のようなアルゴリズムを使う
        // S = {0,...,N-1} を S1,S2 に 2 分割する
        // S1 での優勝者は S2 の優勝者に勝てるとき S で優勝できる
        // よって再帰的分割・統合をくりかえしていけばよい。
        //
        vector<vector<ll>> dp((1<<N), vector<ll>(N,-1));
        function<void(int,int)> dfs = [&](int n, int S) {
            if (n == 1)
            {
                REP(i,N)
                {
                    if ( S & (1<<i) )
                        dp[S][i] = 1;
                    else
                        dp[S][i] = 0;
                }
                return;
            }
            if (dp[S][0] >= 0)  // すでに計算済みなら終了(memo化)
                return;

            fill(RANGE(dp[S]), 0);
            int k = n/2;
            int comb = (1<<k)-1;    // S の 1 ビット集合のサイズ k の部分集合
            while ( comb < (1<<n) ) // 長さ k のビット部分集合 comb ごとにループする
            {
                // S を2つに分割する
                int S1 = 0;
                int S2 = 0;
                int l = 0;
                REP(i, N)
                {
                    if ((1<<i) & S)
                    {
                        if (comb & (1<<l))
                            S1 |= (1<<i);
                        else
                            S2 |= (1<<i);
                        ++l;
                    }
                }
                dfs(k,S1);
                dfs(k,S2);

                vector<int> ai;
                vector<int> bi;
                // index 振り分け
                REP(i, N)
                {
                    if ( S1 & (1<<i) )
                        ai.push_back(i);
                    else if ( S2 & (1<<i) )
                        bi.push_back(i);
                }
                assert(ai.size()==bi.size() && k == ai.size());
                // S1 の優勝者と S2 の優勝者とを比較
                REP(i, k)
                REP(j, k)
                {
                    int ii = ai[i];
                    int jj = bi[j];
                    // S の 1bit は S1 にも S2 にも振り分けられるので
                    // <--S1-->
                    // 1,2,3,4,...
                    // 2,1,3,4,...
                    // のようにグループに所属する i が同じでも場所を変えたケースも
                    // ちゃんと計算されている。
                    if (wins[ii][jj] == 'Y')
                        dp[S][ii] += dp[S1][ii]*dp[S2][jj];
                    if (wins[jj][ii] == 'Y')
                        dp[S][jj] += dp[S1][ii]*dp[S2][jj];
                }

                int x = comb & -comb;
                int y = comb + x;
                comb = (((comb & ~y) / x) >> 1) | y;
            }
            return;
        };
        dfs(N,(1<<N)-1);
        vector<ll> ans;
        REP(i,N)
            ans.push_back(dp[(1<<N)-1][i]);
        return ans;
}
};