题目大意;
给定一个连续的序列,A和B进行博弈,两个人轮流取数,A先取数,每次只能从序列的右端或者左端取一个数,不可以两端都取。所有数字被取完后游戏结束,统计每个人取走的所有数之和作为各自的得分.两人采取的策略都是让自己的得分尽量高,并且两人都十分聪明。
分析
博弈问题
这个题百思不得其解,尤其是关于状态转移的,不是规定取一个数为什么会会有那么多状态,先记下,回头在看看.
整数的总和是一定的,所以一个人的得分越高,相应的另一个人就越低,不管怎么取,对于任意时刻,游戏的状态是原始序列的一个子序列, 我们用d(i,j)表示序列的第i-j个元素组成的序列,在双方都采取最优策略的情况下,先手得分的最大值,;
状态转移时,我们需要枚举从左边取,还是从右边取以及需要取多少个,这等于说枚举给对方留下了什么样的子序列 是(k,j)(i<k<=j)还是(i,k)(i<=k<j) .因此状态转移方程如下;
d(i,j)=sum(i,j)-min{d(i+1,j),d(i+2,j)……d(j,j),d(i,j-1),d(i,j-2)……d(i,i),0}
sum(i,j)表示从i至j个序列的和;
0表示取完所有的数;
两人得分之和为sum(i,j),因此另外一人得分为sum(i,j)-d(i,j) 结果是d(i,j)-( sum(i,j)-d(i,j) )
转化为2*d(i,j)-sum(i,j)
核心代码
int dp(int i,int j) {
if (vis[i][j]) return d[i][j]; //记忆化搜索
v[i][j] = 1;
int m = 0;
for (int k = i + 1; k <= j; k++) m = min(d(k, j), m);
for (int k = j-1; k >=i; k--) m = min(d(i, k), m);
d[i][j] = sum[j] - sum[i - 1] - m;
return d[i][j];
}
回顾一下状态转移方程
d(i,j)=sum(i,j)-min{d(i+1,j),d(i+2,j)……d(j,j),d(i,j-1),d(i,j-2)……d(i,i),0}
我们令
f(i,j)=min{d(i,j),d(i+1,j),……d(j,j)}
g(i,j)=min{d(i,j),d(i,j-1)……d(i,i)}
于是乎,状态转移方程可以写成:
d(i,j) =sum(i,j)-min{f(i+1,j),g(i,j-1),0} f(i,j)和g(i,j)也可以快速计算出来
f(i,j)=min{d(i,j),f(i+1,j)}
g(i,j)=min{d(i,j),g(i,j-1)}
核心代码
void solve()
{
for (int i = 1; i <= n; i++) f[i][i] = g[i][i] = d[i][i] = q[i];
for (int len = 1; len < n; len++)
{
for (int i = 1; len + i <= n; i++)
{
int j = i + len;
int m = 0;
m = min(m, f[i+1][j]);
m = min(m, g[i][j - 1]);
d[i][j] = s[j]-s[i]-m;
f[i][j] = min(f[i + 1][j], d[i][j]);
g[i][j] = min(g[i][j - 1], d[i][j]);
}
}
}