主要是状态不好想,一开始想的博弈dp
dpA[i][L],表示当前区间[L,N]先手拿i个的最优值;
dpB[j][L],表示当前区间后手拿j个的最优值。
然后用先手i,控制后手j的数量。
总是WA,后来想明白了,题读错了,
原因是M值的原因,这道题M一旦变大就不会变小。
而我的这种做法适合后手只依据前手拿的数量,即M值会根据先手变大或变小。
附上这种思路的代码:
// WA掉
int stoneGameII(vector<int>& piles){
int N = piles.size();
int dp[N+1][N+1]; // 表示dp[i][L],区间[L,N]上先手拿i个的最优值
memset(dp,0,sizeof(dp));
int sum[N+1];
memset(sum,0,sizeof(sum));
for(int i=N-1;i>=0;i--) sum[i] = sum[i+1] + piles[i];
// 枚举区间
for(int L=N-1;L>=0;L--){
// 枚举先手,先手最大为剩下所有石头
for(int i=1;i<=N-L;i++){
// 枚举后手,后手最大为剩下所有石头,若石头没有,则后手可以为0
int t = 0;
for(int j=0;j<=min(i*2,N-L-i);j++){
t = max(t,dp[j][L+i]);
}
dp[i][L] = sum[L] - t;
}
}
return max(dp[1][0],dp[2][0]);
}
后手不能只依据先手拿的数量,还要依据M值,也就是说,只要先手拿不超过M,那后手可以拿的数量是不会变的。
但是这个dp区间需要从后往前扫,所以前面M值是未知的,不好处理状态。
所以一种直接的做法就是,以M和L作为状态,dp[m][L]表示当前区间[L,N]在上一次M更新为m时的最优解。
AC代码如下:
// AC
class Solution {
public:
int stoneGameII(vector<int>& piles){
int N = piles.size();
int dp[N+1][N+1]; // dp[M][L] 表示当前区间[L,N]上,当M值时的最大
memset(dp,0,sizeof(dp));
int sum[N+1];
memset(sum,0,sizeof(sum));
for(int i=N-1;i>=0;i--) sum[i] = sum[i+1] + piles[i];
// 枚举左边界L
for(int L=N-1;L>=0;L--){
// 枚举上次生成的M值
for(int M=1;M<N;M++){
// 枚举当前拿x个
for(int x=1;x<=min(N-L,2*M);x++){
dp[M][L] = max(dp[M][L],sum[L] - dp[max(x,M)][L+x]);
}
}
}
return dp[1][0];
}
};