SRM 666 Div2 Hard CollectingTokens

TopCoder Statistics - Problem Statement

  • 復習。復習。
  • 本番全く手が付けられなかったので、解説記事を見て書いた。
  • 木の探索途中で部分 dp をやる。
  • 与えられたグラフが非有向グラフなので par!=u の判定が必要なんだけど、dp 側でうっかりわすれてバグった。
  • 葉ノードの判定で m==0 のケースが抜けて WA してしまった。
  • こういうコードを簡単に書いて、一発で通せるようになりたい。
class CollectingTokens {
public:
int maxTokens(vector <int> A, vector <int> B, vector <int> tokens, int L) {
        int n = A.size()+1;
        vector<vector<int>> tree(n);
        REP(i, n-1)
        {
            tree[A[i]-1].push_back(B[i]-1);
            tree[B[i]-1].push_back(A[i]-1);
        }
        //
        // dp[v][l][b] := 頂点 v からたどるとき、残りの移動可能回数が l のときの最大取得トークン数
        //                ただし b==1 のときは v に戻ってくる
        //
        // 一部の子ノードには進まないという選択もある。
        //
        vector<vector<vector<int>>> dp(n,vector<vector<int>>(L+1,vector<int>(2,0)));
        function<void(int,int)> dfs = [&](int v, int par) {
            int m = tree[v].size();
            if (m == 0 || (m == 1 && tree[v][0] == par)) // 葉ノード(tree が 1 node だけのケースもある)
            {
                REP(b,2)
                REP(l, L+1)
                    dp[v][l][b] = tokens[v];
                return;
            }
            // 子の dp を更新
            for (auto u : tree[v])
                if (u != par)
                    dfs(u, v);
            // 自身の dp を更新
            //  * 一方通行の子は一つのみ。それはどれか?
            //  * どの子をたどるか?
            // dpdp[i][j][l] := 残りの移動可能回数が l のときi 番目までの子を見た時、
            //                j 番目で一方通行になる場合の最大取得トークン数
            vector<vector<vector<int>>> dpdp(m+1,vector<vector<int>>(m+1,vector<int>(L+1,0)));

            REP(i,m)   // m までの子のうちどれをたどるか?
            REP(j,m+1)   // どれが一方通行になるか?
            REP(k,L+1) // 残りの移動回数
            {
                int u = tree[v][i];
                dpdp[i+1][j][k] = max(dpdp[i+1][j][k], dpdp[i][j][k]); // i 番目を訪れないケース
                if (u == par) // 親にもどるものは飛ばす
                    continue;
                if (j < m) // 一方通行になるケース
                {
                    if (i == j) // j 番目で一方通行になる
                    {
                        for (int kk = 1; kk <= k; ++kk)
                            dpdp[i+1][j][k] = max(dpdp[i+1][j][k], dpdp[i][j][k-kk]+dp[u][kk-1][0]);
                    }
                    else
                    {
                        for (int kk = 2; kk <= k; ++kk)
                            dpdp[i+1][j][k] = max(dpdp[i+1][j][k], dpdp[i][j][k-kk]+dp[u][kk-2][1]);
                    }
                }
                else // 一方通行にならないケース
                {
                    for (int kk = 2; kk <= k; ++kk)
                        dpdp[i+1][m][k] = max(dpdp[i+1][m][k], dpdp[i][m][k-kk]+dp[u][kk-2][1]);
                }
            }
            REP(i,m+1)
            REP(k,L+1)
            {
                int b = (i==m)? 1 : 0;
                dp[v][k][b] = max(dp[v][k][b], dpdp[m][i][k] + tokens[v]);
            }
            return;
        };
        dfs(0, -1);
        return dp[0][L][0];
}
};