Game of Sum
思路:
dp[i][j]表示在区间i~j内先手所能获得的最大利益;
ls[i][j]表示先手从左边拿完后,在剩下的区间i~j内,后手所能获得的最小利益;
rs[i][j]表示先手从右边拿完后,在剩下的区间i~j内,后手所能获得的最小利益;
则dp[i][j]=sum[i][j]-min(min(ls[i+1][j],rs[i][j-1]),0);
解释一下上述式子,先手拿共有三种情况:
1.从左边拿1个,则后手剩余ls[i+1][j]
2.从右边拿1个,则后手剩余rs[i][j-1]
3.直接拿完,后手剩余0
问:为什么只拿一个?
答:ls[i][j]=min(dp[i][j],ls[i+1][j]);
rs[i][j]=min(dp[i][j],rs[i][j-1]);
一个只是形式上的一个,因为ls[i][j]和rs[i][j]在动态地更新最小值,也就是说
dp[i][j]=sum[i][j]-min(min(ls[i+1][j],rs[i][j-1]),0)得到的是在区间i~j内拿k(1<=k<=j-i)个的最大利益
最后答案ans=dp[1][n]-(sum[1][n]-dp[1][n])=2*dp[1][n]-sum[1][n](具体详见代码)
代码:
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int dp[110][110],sum[110],ls[110][110],rs[110][110];
int n;
int main()
{
int x;
while(~scanf("%d",&n),n)
{
for(int i=1; i<=n; ++i)
{
scanf("%d",&x);
sum[i]=sum[i-1]+x;
dp[i][i]=ls[i][i]=rs[i][i]=x;
}
for(int l=1; l<n; ++l)
{
for(int i=1; i<=n-l; ++i)
{
int j=i+l;
dp[i][j]=sum[j]-sum[i-1]-min(min(ls[i+1][j],rs[i][j-1]),0);
ls[i][j]=min(dp[i][j],ls[i+1][j]);
rs[i][j]=min(dp[i][j],rs[i][j-1]);
}
}
printf("%d\n",2*dp[1][n]-sum[n]);
}
return 0;
}
看不懂?没关系,下面有个简单的
代码:
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int dp[110][110],sum[110];
int n;
int main()
{
int x;
while(~scanf("%d",&n),n)
{
for(int i=0; i<=n; ++i)
for(int j=0; j<=n; ++j)
dp[i][j]=-999999999;
for(int i=1; i<=n; ++i)
{
scanf("%d",&x);
sum[i]=sum[i-1]+x;
dp[i][i]=x;
}
for(int l=1; l<n; ++l) //区间间隔
for(int i=1; i+l<=n; ++i) //起点
{
int j=i+l;//终点
for(int k=i; k<j; ++k) //先手拿1~n-1的情况
dp[i][j]=max(dp[i][j],sum[j]-sum[i-1]-min(dp[i][k],dp[k+1][j]));//从左边拿,从右边拿
dp[i][j]=max(dp[i][j],sum[j]-sum[i-1]);//先手拿完的情况
}
printf("%d\n",2*dp[1][n]-sum[n]);
}
return 0;
}