Typical DP Contest - B ゲーム

B: ゲーム - Typical DP Contest | AtCoder

  • どうやって最適化戦略をとった状態を表現するのか?
  • すぬけ君の得点を dp 対象にして、すぬけ君はそれを最大化・すめけ君はそれを最小化しようとする。
  • 山の高さが i,j のとき先方の得点を dp 対象にして、それを最大化させるようにする。
  • メモ化解法
int dp[1001][1001]; // Aがi番目,Bがj番目まで取り出された時のすぬけ君の得点の最大値
vector<int> a;
vector<int> b;
class b_game {
public:
    // すぬけ君の得点を返す
    int rec(int i, int j, int k) {
            if (dp[i][j] >= 0)
                return dp[i][j];
            int A = a.size();
            int B = b.size();

            if (i == A && j == B)
                return 0;

            int ret;
            if (k) // すめけ君のターン,すぬけ君の得点を最小化する
            {
                ret = (1<<30);
                // 取得した a[i]/b[j] はすぬけ君の得点となるので加算されない
                if (i < A)
                    ret = min(ret, rec(i+1,j,k^1));
                if (j < B)
                    ret = min(ret, rec(i,j+1,k^1));
            }
            else
            {
                ret = 0;
                if (i < A)
                    ret = max(ret, rec(i+1,j,k^1)+a[i]);
                if (j < B)
                    ret = max(ret, rec(i,j+1,k^1)+b[j]);
            }
            return (dp[i][j] = ret);
    }
    void solve(void) {
            int A,B;
            cin>>A>>B;
            a.resize(A);
            b.resize(B);
            REP(i,A) cin>>a[i];
            REP(i,B) cin>>b[i];

            memset(dp, -1, sizeof(dp));
            cout<<rec(0,0,0)<<endl;
    }
};
  • DP 解法
class b_game {
public:
    void solve(void) {
            int A,B;
            cin>>A>>B;
            vector<int> a(A);
            vector<int> b(B);
            REP(i,A) cin>>a[i];
            REP(i,B) cin>>b[i];

            // sa[i] := Aの残りが i 個のときの山の得点の総和
            vector<int> sa(A+1,0);
            vector<int> sb(B+1,0);

            REP(i,A) sa[i+1] = sa[i]+a[A-1-i];
            REP(i,B) sb[i+1] = sb[i]+b[B-1-i];
            //
            // 部分問題を DP 化
            // dp[i][j] := Aがのこり i 個,Bがのこり j 個の時、自ターンの人の得点の最大値
            // この値は snuke か sumeke に関係なく自ターンの人のものになる。
            // 互いに最善を尽くすときは同じ操作になるはず。
            //
            vector<vector<int>> dp(A+1,vector<int>(B+1,0));

            // 意味的には山が空になっている状態から逆にたどる
            REP(i, A+1)
            REP(j, B+1)
            {
                int sum = sa[i]+sb[j];
                //
                // 交互にターンが切り替わるので、更新前の現在の自分の得点は
                // sum-dp[i][j] となる。
                //
                if (i < A)
                    dp[i+1][j] = max(dp[i+1][j], sum-dp[i][j]+a[A-i-1]);
                if (j < B)
                    dp[i][j+1] = max(dp[i][j+1], sum-dp[i][j]+b[B-j-1]);
            }
            cout<<dp[A][B]<<endl;
    }
};