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

来源:互联网 发布:中控考勤机数据修改 编辑:程序博客网 时间:2024/05/16 23:37
例题28 Sum游戏(Game of Sum, UVa 10891
有一个长度为
n的整数序列, 两个游戏者AB轮流取数,A先取。 每
次玩家只能从左端或者右端取一个数, 但不能两端都取。 所有数都被取
走后游戏结束, 然后统计每个人取走的所有数之和, 作为各自的得分。
两个人采取的策略都是让自己的得分尽量高, 并且两人都足够聪明, 求
A
的得分减去B的得分后的结果。
【输入格式】
输入包含多组数据。 每组数据的第一行为正整数
n1≤n≤100) , 第
二行为给定的整数序列。 输入结束标志为
n0
【输出格式】
对于每组数据, 输出
AB都采取最优策略的情况下,A的得分减去B
的得分后的结果。
【分析】
整数的总和是一定的, 所以一个人得分越高, 另一个人的得分就越
低。 不管怎么取, 任意时刻游戏的状态都是原始序列的一段连续子序列
(即被两个玩家取剩下的序列) 。 因此, 我们想到用
dij) 表示原序
列的第
ij个元素组成的子序列(元素编号为1n) , 在双方都采取最优
策略的情况下, 先手得分的最大值(只考虑
ij这些元素) 。
状态转移时, 我们需要枚举从左边取还是从右边取以及取多少个。
这等价于枚举给对方剩下怎样的子序列: 是(
kj) (ik≤j) , 还是
ik) (i≤kj) 。 因此:
其中,
sumij) 是元素i到元素j的数之和。 注意, 这里的“0”
完所有数
的决策, 有了它, 方程就不需要显式的边界条件了。
两人得分之和为
sum1n) , 因此答案是d1n) -(sum1
n) -d1n) ) =2d1n) -sum1n) 。 注意, sumij) 的计
算不需要循环累加, 可以预处理
S[i]为前i个数之和, 则sumij) =S[j]
S[i1]

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

/*状态有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[n]);//dp[1,n]-(s[n]-dp[1,n])=2*dp(1,n)-s[n]    }    return 0;}

状态有On2) 个, 每个状态有On) 个转移, 所以时间复杂度为
On3) , 空间复杂度为On2) 。 对于本题的规模, 这样的时间复杂度已
经不错了, 但其实还可以进一步改进。 让我们回顾一下状态转移方程:
如果令
fij) =mindij) , di1j) , dj
120
j) } , gij) =mindij) , dij1) ,dii) } , 则
状态转移方程可以写成:
fg也可以快速递推出来:fij) =mindij) , fi1
j) } , gij) =mindij) , gij1) } , 因此每个fij
的计算时间都降为了
O1) 。 下面我们用递推(而非记忆化搜索) 的方
法编写。 代码如下。


//递推解法,时间复杂度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][n]-s[n]);}return 0;}


原创粉丝点击