关于动态规划的一点小想法

本文最后更新于 2024年4月9日 下午

最近一直在做动态规划的题,也算有了一些自己的小感悟,暂时总结一下


首先看一道例题

题目链接

描述

X 国王有一个地宫宝库。是 n x m 个格子的矩阵。每个格子放一件宝贝。每个宝贝贴着价值标签。

地宫的入口在左上角,出口在右下角。

小明被带到地宫的入口,国王要求他只能向右或向下行走。

走过某个格子时,如果那个格子中的宝贝价值比小明手中任意宝贝价值都大,小明就可以拿起它(当然,也可以不拿)。

当小明走到出口时,如果他手中的宝贝恰好是 k 件,则这些宝贝就可以送给小明。

请你帮小明算一算,在给定的局面下,他有多少种不同的行动方案能获得这 k 件宝贝。

输入

输入一行 3 个整数,用空格分开:n m k (1<=n,m<=50, 1<=k<=12)

接下来有 n 行数据,每行有 m 个整数 Ci (0<=Ci<=12)代表这个格子上的宝物的价值

输出

要求输出一个整数,表示正好取 k 个宝贝的行动方案数。该数字可能很大,输出它对 1000000007 取模的结果。

1
2
3
4
5
6
7
输入样例1  
2 2 2
1 2
2 1

输出样例1
2
1
2
3
4
5
6
7
输入样例2
2 3 2
1 2 3
2 1 5

输出样例2
14

思考

  1. 动态规划,我们知道这是一种记录 T 时刻状态,通过状态转移方程,推导出 T+1 时刻的状态的算法,是一种空间换时间的算法,状态的记录通常使用数组。
  2. 求解目标:解决动态规划的问题,首先我们需要明确求解的目标是什么?求解目标就是数组元素的值。上面这道例题中,求解的目标就是到达终点满足条件的方法数目。那么数组元素中记录的值就是方法数。
  3. 数组:其次我们需要解决的是数组的维数,这个问题我们可以通过观察变量的数目来确定。上面的例题中,影响结果的变量有:横 m、纵坐标 n、物品最大价值 mp、物品数目 k 四个,所以需要引入的是一个数组四维数组。
  4. 起点:既然是根据前项推出后项,那么如同递归需要有一个出口一样,动态规划需要至少有一个起点——也即数组的某些元素的值是可以在初始阶段确定的。
    • 在上面的例题中,(1,1,0,0)元素的值可以确定(到达终点(1,1)位置,一个元素都不拿,物品的最大价值为 0,明显只有一种方法)
    • 同时(1,1,1,goods[1][1])元素的值也是能确定(只拿一个物品,那么只能拿(1,1)这个位置的物品,拿了之后物品最大价值的就是(1,1)的价值)
  5. 状态转移方程:最后需要明确的就是状态转移方程,在上面的例题中,到达一个位置拿 k 个物品,有两种方式。
    • 不拿当前的物品,那么

      Num[i][j][k][mp]+=Num[i-1][j][k][mp]+Num[i][j-1][k][mp]

    • 当前位置的物品价值大于已经拿到的最大价值的情况下,那么可以拿起当前物品。但请注意拿起后物品数加一,已经拿到的最大价值也改变。

      Num[i][j][k+1][goods[i][j]]+=Num[i-1][j][k][mp]+Num[i][j-1][k][mp]


到这里这道题的总体思路也就解决了,除此外到达终点的物品最大价值可能有多种情况,只要将所有价值情况下的方法数求和即可。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

#define ll long long
#define mod 1000000007
#include <iostream>
#include <cstring>

using namespace std;

//物品分布情况
int graph[51][51];

//横坐标、纵坐标、物品数量、最大价值
//数组的值表示取法有多少种
ll num[51][51][13][13];

int main()
{
int n, m, k;
cin >> n >> m >> k;

memset(graph, 0, sizeof(graph));
memset(num, 0, sizeof(num));

int max_price = 0;
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= m; ++j)
{
cin >> graph[i][j];
graph[i][j]++;
max_price = max_price < graph[i][j] ? graph[i][j] : max_price;
}
}

num[1][1][0][0] = 1;
num[1][1][1][graph[1][1]] = 1;

for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= m; ++j)
{
for (int l = 0; l <= k; ++l)
{
for (int m = 0; m <= max_price; ++m)
{
if (i != 1)
{
num[i][j][l][m] += num[i - 1][j][l][m];
num[i][j][l][m] %= mod;
if (graph[i][j] > m)
{
num[i][j][l + 1][graph[i][j]] += num[i - 1][j][l][m];
num[i][j][l + 1][graph[i][j]] %= mod;
}
}
if (j != 1)
{
num[i][j][l][m] += num[i][j - 1][l][m];
num[i][j][l][m] %= mod;
if (graph[i][j] > m)
{
num[i][j][l + 1][graph[i][j]] += num[i][j - 1][l][m];
num[i][j][l + 1][graph[i][j]] %= mod;
}
}
}
}
}
}
int ans = 0;
for (int i = 0; i <= max_price; i++)
{
ans = (ans + num[n][m][k][i]) % mod;
}
cout << ans << endl;
return 0;
}

关于动态规划的一点小想法
https://siegelion.cn/2020/05/08/关于动态规划的一点小想法/
作者
siegelion
发布于
2020年5月8日
许可协议