动态规划篇——记忆化搜索与递推

  • 记忆化搜索与递推

定义

在使用动态规划的时候,我们可能会对某些状态进行重复访问。实际上,我们可以通过存储访问数据的方式,避免重复访问某些状态时的重复计算,达到在时间上优化算法的目的。

关于记忆化搜索的联系与区别。

例题

【例 1】 Function

【例 2】 三倍经验

例题分析

【例 1】

分析

近乎是一道裸的 DP (橙题能有什么弯)。但是,如果使用最朴素的 DP,递归的次数将会非常多。

TLE记录

所以,我们要引入记忆化数组,将递归过程中得到的信息储存。再次访问这些数据时,就能避免重复递归(这是一种空间换时间的思想)。

代码

#include <iostream>
using namespace std;
long long remember[25][25][25];
long long w(long long a, long long b, long long c) {
	if (a <= 0 || b <= 0 || c <= 0) {
		a = 0;
		b = 0;
		c = 0;
	}
	if (a > 20 || b > 20 || c > 20) {
		a = 20;
		b = 20;
		c = 20;
	}
	if (remember[a][b][c]) {
		return remember[a][b][c];
	}
	else {
		if (a <= 0 || b <= 0 || c <= 0) {
			remember[a][b][c] = w(0, 0, 0);
		}
		else if (a > 20 || b > 20 || c > 20) {
			remember[a][b][c] = w(20, 20, 20);
		}
		else if (a < b && b < c) {
			remember[a][b][c] = w(a, b, c - 1) + w(a, b - 1, c - 1) - w(a, b - 1, c);
		}
		else {
			remember[a][b][c] = w(a - 1, b, c) + w(a - 1, b - 1, c) + w(a - 1, b, c - 1) - w(a - 1, b - 1, c - 1);
		}
		return remember[a][b][c];
	}
}
int main() {
	long long a, b, c;
	remember[0][0][0] = 1;
	while (cin >> a >> b >> c) {
		if (a == -1 && b == -1 && c == -1) {
			break;
		}
		else {
			cout << "w(" << a << ", " << b << ", " << c << ") = ";
			cout << w(a, b, c) << endl;
		}
	}
	return 0;
}

【例 2】

分析

首先,如果没有“同时作为一个强大的小朋友,你可以选择金字塔中的不多于 \(k\) 个数字让他们成为原来的 \(3\) 倍”这一条件,问题则变成[USACO1.5] [IOI1994]数字三角形 Number Triangles

对于这个简化的问题,我们作如下分析:每个点的“来路”为其左上与右上的点,那么我们只需要设 \(f(i,j)\) 为路径在第 \(i\) 行第 \(j\) 列处结束的最大值,可以写出状态转移方程:

\[f(i,j) = max(f(i-1,j),f(i-1,j-1))+a_{ij} \]

其中 \(a_{ij}\) 即为第 \(i\) 行第 \(j\) 列的数。

回到本题。本题的难点即在我们可以选择不多于 \(k\) 个数字让他们成为原来的 \(3\) 倍(实际上,如果我们选择的次数大于我们的列数,这些多出的次数是没有用的)。我们可以将状态转移方程改为三维的,来描述已经完成的选择的次数。状态转移方程也可以类似地写出:

\[f(i,j,k)=max(f(i,j,k),f(i-1,j,k)+a_{ij},f(i-1,j-1,k)+a_{ij}) \]

\[f(i,j,k+1)=max(f(i,j,k+1),f(i-1,j,k)+3 \times a_{ij},f(i-1,j-1,k)+3 \times a_{ij}) \]

最后我们只需要在所有的 \(f(n,j,k)\) 中找到最大的即可(即遍历最下面一层金字塔的每一个点,对每一个点遍历选择的次数)。

代码

#include <iostream>
#define int long long
using namespace std;
int dp[105][105][105];//dp[i][j][k]表示在第i行第j列,使用k次能力之后的最大值
int a[105][105];
signed main()
{
	int n, _k;
	cin >> n >> _k;
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= i; j++)cin >> a[i][j];
	}
	for (int i = 0; i <= 100; i++)
	{
		for (int j = 0; j <= 100; j++)
		{
			for (int k = 0; k <= 100; k++)dp[i][j][k] = -1e10;
		}
	}
	dp[1][1][0] = a[1][1];
	dp[1][1][1] = 3 * a[1][1];
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= i; j++)
		{   
			for (int k = 0; k <= min(_k, i); k++)
			{
				dp[i][j][k] = max(dp[i][j][k], max(dp[i - 1][j][k] + a[i][j], dp[i - 1][j - 1][k] + a[i][j]));
				dp[i][j][k + 1] = max(dp[i][j][k + 1], max(dp[i - 1][j][k] + 3 * a[i][j], dp[i - 1][j - 1][k] + 3 * a[i][j]));
			}
		}
	}
	
	int ans = dp[n][1][0];
	for (int j = 1; j <= n; j++)
	{
		for (int k = 0; k <= min(_k, n); k++)
		{
			ans = max(ans, dp[n][j][k]);
		}
	}
	cout << ans << endl;
	return 0;


}
posted @ 2023-08-16 01:46  susenyang  阅读(202)  评论(0)    收藏  举报  来源