Typical DP Contest - R グラフ

R: グラフ - Typical DP Contest | AtCoder

  • 強連結成分分解とtopological sort が面倒だった。
  • dp[i][j] を最大にする path2 の経路の保存が必要か?と思いきやそんなこともなかったというのがミソなんだと思った。
class r_graph {
public:
    SCC scc_;
    //
    // 二つのパスの重なり部分は得点が加算されない。
    // この重なりはどうやって考えたらよいか?
    //
    void solve(void) {

            int N;
            cin>>N;
            scc_.init(N);

            // まず強連結成分分解をして DAG にする
            REP(i, N)
            {
                int has_edge;
                REP(j, N)
                {
                    cin>>has_edge;
                    if (has_edge) scc_.add(i,j);
                }
            }
            // 強連結成分分解
            int K = scc_.decompose();
            auto cmp = scc_.components();
            const auto &G = scc_.G_; // scc で入力にしたグラフを再利用

            // グラフを再構築
            // 強連結成分を重みをつけたグラフに縮約して DAG にする
            //
            //    * -> *
            //    ^    v
            // -> * <- *    =>  -> [4]
            //    |                 |
            //    +->...            +->...
            //
            vector<int> score(K, 0); // 重み
            REP(i, N)
                ++score[cmp[i]];

            vector<vector<int>> reduced(K);
            vector<vector<bool>> move(K, vector<bool>(K,false)); // rel[i][j] := i -> j へ移動できるか?
            // まず隣接する場所にだけフラグを建てる
            REP(i,N)
            for (auto j : G[i])
                move[cmp[i]][cmp[j]] = true;

            // 縮約グラフ作成
            REP(i, K)
            REP(j, K)
                if (move[i][j] && i != j) // 自分自身へのループはなし
                    reduced[i].push_back(j);

            // i -> j の移動が可能かの判定用に補完する
            REP(k, K)
                move[k][k] = true;

            REP(k, K)
            REP(i, K)
            REP(j, K)
                move[i][j] = move[i][j] || (move[i][k] && move[k][j]);

            // topological sort して根から DP する。
            vector<int> ord = topological_sort(reduced);

            //
            //  * 二本のパスはともに DAG の根からスタートさせる
            //
            //  0 -> 1 -> 2   3 -> 4 -> 5 -> ...
            //       |             ^
            //       +-------------+
            //
            // のように途中でパスの始点が変わる(0 or 3)ほうが得点が大きくなることも
            // ありうるが根(0)からスタートさせれば全てのケースを見れる。
            //
            // dp[i][j] := path1 の先頭が i で、path2 の先頭が j のときの最大スコア
            //             (i,j は topological_sort での順番)
            //
            // とし、この DP の更新を考える。
            //
            // i のノードが i1,i2,...,k,... に分岐しているとき
            //
            // A) k が j の先祖でなければそのま更新
            //  dp[k][j] = dp[i][j] + score[k]
            //
            // B) k が j の先祖なら
            //  1) dp[i][j] を最大にする path2 の経路上に k があるとき
            //  2) dp[i][j] を最大にする path2 の経路上に k がないとき(別経路がある)
            //
            // 1) ならすでに色がぬられているので dp[k][j] = dp[i][j]
            // 2) ならそのまま伸ばせるので A) と同じ
            //
            // => この判定のために dp[i][j] を最大にする path2 の経路の保存が必要か?
            //
            // 実は必要ない。
            // B) が起こる状況ではトポロジカルソートの順で k <= j となっている。
            //  よって 2) のケースは i,j を逆にしたときに計算されている
            //  1)の場合、 dp[k][j] = dp[i][j] そのもの。
            //
            vector<vector<int>> dp(K, vector<int>(K,0));
            vector<int> rord(K,0);
            REP(i, K)
                rord[ord[i]] = i;

            REP(i, K)
            REP(j, K)
            {
                //
                //     ki          kj
                //      \         /
                // ki'-- i - - - j - kj'
                //

                // まず i-j 間を評価
                if (i != j)
                    dp[i][j] = max(dp[i][j], score[ord[i]] + score[ord[j]]);
                else
                    dp[i][i] = max(dp[i][i], score[ord[i]]);

                int ni = ord[i];
                int nj = ord[j];
                // i から先を更新
                for(auto ki : reduced[ni])
                {
                    int k = rord[ki];
                    assert(k>i);  // topological sort 済みなので順序が保たれている
                    if (move[ki][nj]) // j-i-k が連結のとき
                        dp[k][j] = max(dp[k][j], dp[i][j]);
                    else
                        dp[k][j] = max(dp[k][j], dp[i][j] + score[ki]);
                }
                // j から先を更新
                for(auto kj : reduced[nj])
                {
                    int k = rord[kj];
                    assert(k>j);
                    if (move[kj][ni])
                        dp[i][k] = max(dp[i][k], dp[i][j]);
                    else
                        dp[i][k] = max(dp[i][k], dp[i][j] + score[kj]);
                }
            }
            int ret = 0;
            REP(i,K)
            REP(j,K)
                ret = max(ret, dp[i][j]);

            cout<<ret<<endl;
    }
};