动态规划篇——记忆化搜索与递推
-
记忆化搜索与递推
定义
在使用动态规划的时候,我们可能会对某些状态进行重复访问。实际上,我们可以通过存储访问数据的方式,避免重复访问某些状态时的重复计算,达到在时间上优化算法的目的。
例题
【例 1】 Function
【例 2】 三倍经验
例题分析
【例 1】
分析
这近乎是一道裸的 DP (橙题能有什么弯)。但是,如果使用最朴素的 DP,递归的次数将会非常多。
所以,我们要引入记忆化数组,将递归过程中得到的信息储存。再次访问这些数据时,就能避免重复递归(这是一种空间换时间的思想)。
代码
#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;
}