您的位置:首页 > 其它

UVa 10891(记忆化搜索,递推)Game of Sum

2017-07-15 08:04 218 查看
例题28 Sum游戏(Game of Sum, UVa 10891)

有一个长度为n的整数序列, 两个游戏者A和B轮流取数,A先取。


次玩家只能从左端或者右端取一个数, 但不能两端都取。 所有数都被取

走后游戏结束, 然后统计每个人取走的所有数之和, 作为各自的得分。

两个人采取的策略都是让自己的得分尽量高, 并且两人都足够聪明, 求A
的得分减去B的得分后的结果。

【输入格式】

输入包含多组数据。 每组数据的第一行为正整数n(1≤n≤100) , 第

二行为给定的整数序列。 输入结束标志为n=0。

【输出格式】

对于每组数据, 输出A和B都采取最优策略的情况下,A的得分减去B
的得分后的结果。

【分析】

整数的总和是一定的, 所以一个人得分越高, 另一个人的得分就越

低。 不管怎么取, 任意时刻游戏的状态都是原始序列的一段连续子序列

(即被两个玩家取剩下的序列) 。 因此, 我们想到用d(i,j) 表示原序

列的第i~j个元素组成的子序列(元素编号为1~n)
, 在双方都采取最优

策略的情况下, 先手得分的最大值(只考虑i~j这些元素) 。

状态转移时, 我们需要枚举从左边取还是从右边取以及取多少个。

这等价于枚举给对方剩下怎样的子序列: 是(k,j) (i<k≤j)
, 还是

(i, k) (i≤k<j)
。 因此:

其中, sum(i,j) 是元素i到元素j的数之和。
注意, 这里的“0”是“取

完所有数”的决策, 有了它, 方程就不需要显式的边界条件了。

两人得分之和为sum(1,n) , 因此答案是d(1,n)
-(sum(1,
n) -d(1,n)
) =2d(1,n) -sum(1,n)
。 注意, sum(i,j) 的计

算不需要循环累加, 可以预处理S[i]为前i个数之和, 则sum(i,j)
=S[j]
-S[i-1]。

下面是完整代码。 它采用了记忆化搜索的方式, 显得更加自然。

/*状态有O(n)个,每个状态有O(n)个转移,所以时间复杂度为O(n^3),空间复杂度为O(n^2)
发现一件事:其实相互博弈就是一个相互递归的过程
可以一次取光,不可以不取
*/
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int mx=105;
int n,a[mx],d[mx][mx],s[mx],vis[mx][mx];
int dp(int i,int j)
{
if(vis[i][j]) return d[i][j];
vis[i][j]=1;
int m=0;//是全部取完,此时就不需要显式的边界了
for(int k=i+1; k<=j; k++) m=min(m,dp(k,j));
for(int k=i; k<j; k++) m=min(m,dp(i,k));
return d[i][j]=s[j]-s[i-1]-m;//这个注意是i-1
}
int main()
{
while(scanf("%d",&n)&&n)
{
for(int i=1; i<=n; i++)
{
scanf("%d",a+i);
s[i]=s[i-1]+a[i];
}
memset(vis,0,sizeof(vis));
printf("%d\n",2*dp(1,n)-s
);//dp[1,n]-(s
-dp[1,n])=2*dp(1,n)-s

}
return 0;
}

状态有O(n2) 个, 每个状态有O(n)
个转移, 所以时间复杂度为
O(n3) , 空间复杂度为O(n2)
。 对于本题的规模, 这样的时间复杂度已

经不错了, 但其实还可以进一步改进。 让我们回顾一下状态转移方程:

如果令f(i,j) =min{d(i,j)
, d(i+1,j)
, …,d(j,
120
j) } , g(i,j)
=min{d(i,j)
, d(i,j-1)
,…, d(i,i)
} , 则

状态转移方程可以写成:
f和g也可以快速递推出来:f(i,j)
=min{d(i,j)
, f(i+1,
j) } , g(i,j)
=min{d(i,j)
, g(i,j-1)
} , 因此每个f(i,j)

的计算时间都降为了O(1) 。 下面我们用递推(而非记忆化搜索) 的方

法编写。 代码如下。

//递推解法,时间复杂度O(n^2),神奇的递推啊,推个公式得推多久啊
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int mx=105;
int n,a[mx],d[mx][mx],s[mx],f[mx][mx],g[mx][mx];
int main(){
while(scanf("%d",&n)&&n)
{
for(int i=1;i<=n;i++)
{
scanf("%d",a+i);
s[i]=s[i-1]+a[i];
}
for(int i=1;i<=n;i++) f[i][i]=g[i][i]=d[i][i]=a[i];//边界
for(int L=1;L<n;L++)
for(int i=1;i+L<=n;i++)
{
int j=i+L;
int m=min(0,min(f[i+1][j],g[i][j-1]));
d[i][j]=s[j]-s[i-1]-m;
f[i][j]=min(d[i][j],f[i+1][j]);
g[i][j]=min(d[i][j],g[i][j-1]);
}
printf("%d\n",2*d[1]
-s
);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: