NKOJ P1835 【USACO3.3.5】A Game游戏 IOI‘96

Description

有如下一个双人游戏:N(2 <= N <= 100)个正整数的序列放在一个游戏平台上,游戏由玩家1开始,两人轮流从序列的两端取数,取数后该数字被去掉并累加到本玩家的得分中,当数取尽时,游戏结束。以最终得分多者为胜。

编一个执行最优策略的程序,最优策略就是使玩家在与最好的对手对弈时,能得到的在当前情况下最大的可能的总分的策略。你的程序要始终为第二位玩家执行最优策略。

Input Format

第一行: 正整数N, 表示序列中正整数的个数。

第二行至末尾: 用空格分隔的N个正整数(大小为1-200)。

Output Format

只有一行,用空格分隔的两个整数: 依次为玩家一和玩家二最终的得分。

Sample Input

6 
4 7 2 9 5 2

Sample Output

18 11

第一次做博弈论的题,写篇文章纪念一下。

题目说的很清楚,两个玩家执行的均是最优策略。

当时拿到这道题就懵逼了:两个都要最优策略?这怎么动规……

然而仔细想了半天,原来和求一个人最大能取到的值没有多大区别,只是对手从采取最差的策略变成了最好的策略。

为什么可以这样理解呢?原来一个人,也就相当于对手采取了最差的策略(毕竟是在听对手摆布)。

现在两个人了,但是代码一毛一样。

让自己的分数更高,其实相当于让对手的分数最少。

所以我们要明白的是,对于当前区间 i , j i,j ,如果轮到2取数,那么1就不能取到 m a x ( f i , j 1 , f i + 1 , j ) max(f_{i,j-1},f_{i+1,j}) ,而是 m i n ( f i , j 1 , f i + 1 , j ) min(f_{i,j-1},f_{i+1,j}) ,因为对手采取了最优策略,他让1只能取到其中的较小者。

(1表示很淦)

以下是具体的动规方案,建议自己思考后再看。

状态:

f i , j f_{i,j} 表示在区间 i , j i,j 内1能取到的最大值。

决策:

上面已经说过,当轮到自己取时有两个决策: f i , j 1 f_{i,j-1} f i + 1 , j f_{i+1,j}

如果轮到对手,1只有一种决策,就是取二者之间的较小值。

状态转移方程:

f i , j = { m a x ( f i , j 1 + a j , f i + 1 , j + a i ) ( 1 ) m i n ( f i , j 1 , f i + 1 , j ) ( 2 ) f_{i,j}=\begin{cases}max(f_{i,j-1}+ a_j,f_{i+1,j}+a_i)(当轮到1取的时候)\\ min(f_{i,j-1},f_{i+1,j})(当轮到2取的时候)\end{cases}

阶段:

显然每个状态依赖于他的子区间,那我们需要按照序列长度划分区间。

先初始化, f i , i f_{i,i} n n 为奇数(轮到1取)时为1,否则为0。

然后,从 2 2 n n 枚举序列长度,用 i i 表示长度,从 1 1 n i + 1 n-i+1 枚举序列起点。

这道题还有一个问题,就是如何判断当前该1取还是该2取?

n i + 1 n - i + 1 为奇数时,显然该 1 1 取。否则,该 2 2 取。

伪代码:

for (i,1……n)
	if (n 是奇数) f[i][i] = a[i];
for (i,2……n)
	for (j,1……n-i+1)
		if (轮到1)
		f[j][序列终点] = max(f[j][序列终点 - 1] + a[序列终点], f[j + 1][序列终点] + a[j]);
		else f[j][序列终点] = min(f[j][序列终点 - 1], f[j + 1][序列终点]);

C o d e : Code:

#include <cstdio>
#include <algorithm>

using namespace std;

int a[105], f[105][105];

int main() 
{
	int n, Sum = 0;
	scanf("%d", &n);
	for (int i = 1; i <= n; i ++)
	scanf("%d", a + i), Sum += a[i];//方便计算玩家2的得分
	for (int i = 1; i <= n; i ++)
	if (n & 1) f[i][i] = a[i];//初始化
	for (int i = 2; i <= n; i ++)//枚举序列长度
	for (int j = 1; j <= n - i + 1; j ++)//枚举起点,注意最靠后的起点位置不要算错
	if ((n - i + 1) & 1)//如果轮到1取,取最大值
	f[j][j + i - 1] = max(f[j][j + i - 2] + a[j + i - 1], f[j + 1][j + i - 1] + a[j]);//方程
	else f[j][j + i - 1] = min(f[j][j + i - 2], f[j + 1][j + i - 1]);//否则由于对手采取最优对策,取最小值
	printf("%d %d", f[1][n], Sum - f[1][n]);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/jvruo_shabi/article/details/108217946