例题1.28 Sum游戏 UVa10891

1.题目描述:点击打开链接

2.解题思路:本题是一道经典的利用递推式优化dp的题目,非常值得学习。首先,根据题意描述:不管怎么取,任何时刻的游戏状态都是原始序列的一段连续子序列,因此,我们想到用d(i,j)表示原序列的第i~j个元素的子序列(元素编号为1~n),在双方都采取最优策略的情况下,先手得分的最大值(只考虑i~j这些元素)。

状态转移时,我们需要枚举从左边取还是从右边取以及取多少个。这等价于给对方剩下怎样的子序列:是(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(1,n),因此答案是d(1,n)-{sum(1,n)-d(1,n)}=2*d(1,n)-sum(1,n)。注意,sum(i,j)的计算可以通过前缀和数组S得到。

下面谈一下重点部分:利用递推式来优化状态转移方程

根据上述的写法,状态有O(n^2)个,每个状态又有O(n)个转移,因此时间复杂度为O(N^3),空间复杂度为O(N^2)。对于本题的规模,这样的复杂度已经够了。但是还可以进一步改进:

如果我们令f(i,j)=min{d(i+1,j),d(i+2,j),...,d(j,j)},g(i,j)=min{d(j,j),d(i,j-1),d(i,j-2),...,d(i,i)},则状态转移方程可以写为:

d(i,j)=sum(i,j)-min{f(i+1,j),g(i,j-1),0}

这里引入f和g函数的目的是因为它们均可以递推:f(i,j)=min{d(i,j),f(i+1,j)},g(i,j)={d(i,j),g(i,j-1)}(由定义即可得到该递推式),因此每个f(i,j)的计算时间都降为O(1)。

下面给出两种不同写法的dp:先给出记忆化搜索的写法

(记忆化搜索写法)

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
#include<algorithm>
#include<string>
#include<sstream>
#include<set>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<functional>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> P;
typedef pair<long long, long long> PL;
#define me(s) memset(s,0,sizeof(s))
#define For(i,n) for(int i=0;i<(n);i++)

const int N = 100 + 10;
int S[N];
int A[N];
int d[N][N];
int vis[N][N];
int n;

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));
	d[i][j] = S[j] - S[i - 1] - m;//如果i从0开始编号,这里要判断一下是否有i==0
	return d[i][j];
}
int main()
{
	//freopen("t.txt", "r", stdin);
	while (~scanf("%d", &n) && n)
	{
		S[0] = 0;
		for (int i = 1; i <= n; i++)
		{
			scanf("%d", &A[i]);
			S[i] = S[i - 1] + A[i];
		}
		me(vis);//注意一定要清空
		printf("%d\n", 2 * dp(1, n) - S[n]);
	}
	return 0;
}

 

(递推写法)

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
#include<algorithm>
#include<string>
#include<sstream>
#include<set>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<functional>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> P;
typedef pair<long long, long long> PL;
#define me(s) memset(s,0,sizeof(s))
#define For(i,n) for(int i=0;i<(n);i++)

const int N = 100 + 10;
int S[N];
int A[N];
int d[N][N];
int f[N][N], g[N][N];
int vis[N][N];
int n;


int main()
{
	//freopen("t.txt", "r", stdin);
	while (~scanf("%d", &n) && n)
	{
		S[0] = 0;
		for (int i = 1; i <= n; i++)
		{
			scanf("%d", &A[i]);
			S[i] = S[i - 1] + A[i];
		}
		me(vis);
		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++)//按照L=j-i递增的顺序写
		for (int i = 1; i + L <= n; i++)
		{
			int j = i + L;
			int m = 0;//注意此处,因为m=min{f(i+1,j),g(i,j-1),0}
			m = min(m, f[i + 1][j]);
			m = min(m, g[i][j - 1]);
			d[i][j] = S[j] - S[i - 1] - m;
			f[i][j] = min(d[i][j], f[i + 1][j]);//递推f和g
			g[i][j] = min(d[i][j], g[i][j - 1]);
		}
		printf("%d\n", 2 * d[1][n] - S[n]);
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值