链接:
https://leetcode.cn/problems/stone-game-ii/
题意:
有lg(piles.length)堆石头,第i堆有piles[i]颗石头
AB轮流操作,可以拿走X堆,X在1~2*M范围,一开始M为1
**每次操作(不是每轮)**后,M变成max(M,X)
解:
到是看出来DP了,正着一直写不出来,问题出在无法正着推出另一个人的最优解
佬说要逆着推
dp【i】【j】表示从i开始取(包含i),当前操作级j (我把M称为操作级)
可以推出两条
(1)当i-1+2*j >= lg 时(因为i本身占一位所以减一)可以把剩下的石头堆全取走,数量为sum[i:lg]
然后先写个前缀和sum
(2)当i-1+2*j < lg 时 ,转移公式为dp【i】【j】=max(dp【i】【j】,sum[i:lg]-dp【i+x】【max(j,x)】)
循环x在1到2*j(遍历所有操作)
sum[i:lg]是剩下全部的石头,而根据x的不同dp【i+x】【max(j,x)】为对方所能选取的数量,要让自己要拿最多,自然剩给对方的最少,所以对相减的值取max
因为是逆推,由于i是第一层循环,j是第二层,所以当遇到这种不能全取走的情况时,所有大于i的状态都已经判断完了,而且每次拿走的X>=1,所以i+x一定有效**(当i==lg,即从最后一堆开始取的时候,不管j是多少都是状态1)**
实际代码:
#include<iostream>
#include<bits/stdc++.h>
using namespace std;
const int Nmax=1E2+3;
int dp[Nmax][Nmax];
int sum[Nmax];
void out(int n)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
cout<<dp[i][j]<<" ";
}
cout<<endl;
}
}
int solve(vector<int>& piles)
{
int lg=piles.size();
//i为选取位置起点_含
for(int i=lg;i>=1;i--)
{
//j为操作等级M,操作数1<=x<=2*M
for(int j=lg;j>=1;j--)
{
//cout<<i<<"->"<<j<<endl;
if(i-1+2*j>=lg)//全取走
{
dp[i][j]=sum[lg]-sum[i-1];
}
else//不能全取走,判断取多少剩给对方最少
{
for(int x=1;x<=2*j;x++)//遍历选择
{
//sum[lg]-sum[i-1]为剩下数字
//dp[i+x][max(j,x)]
//i+x为新的起点
//cout<<dp[i][j]<<" "<<sum[lg]-sum[i-1]<<" "<<dp[i+x][max(j,x)]<<endl;
dp[i][j]=max(dp[i][j],sum[lg]-sum[i-1]-dp[i+x][max(j,x)]);
}
}
}
}
//out(lg);
return dp[1][1];
}
int main()
{
int n;cin>>n;
vector<int> piles;
for(int i=1;i<=n;i++)
{
int temp;cin>>temp;
piles.push_back(temp);
//前缀和
if(i) sum[i]=sum[i-1]+temp;
//cout<<"i:"<<sum[i]<<endl;
}
int ans=solve(piles);
cout<<ans<<endl;
}
限制:
1 <= piles.length <= 100
1 <= piles[i] <= 104