四维的dp
地宫取宝
题目链接:https://www.acwing.com/problem/content/1214/
视频讲解:https://www.acwing.com/video/638/
问题分析:
读题,很容易发现是一个典型的背包问题,用f表示路径的行动方案数,相较于01背包问题,该题多了一维,所以用两维来表示位置,f(i, j),继续往下读,又有限制手里的宝贝是k件,并且走到(i,j)位置时,需要判断手里的宝贝的情况————“走过某个格子时,如果那个格子中的宝贝价值比小明手中任意宝贝价值都大,小明就可以拿起它(当然,也可以不拿)。”所以,再增加两维——(k,l)。至此题目读完。
dp问题分析法:
第一步:状态表示
由于读题,我们知道这个题需要四维,f(i, j, k, l),明确每一个元素的意义,i和j很清楚,表示坐标,k代表手里物品的个数,l表示手里最贵重物品的价值。
第二步:状态计算:
我们研究一种情况:现在已经走到了(i,j)处,手里有k件物品,最大价值是l,现在分析当前要怎样做。
1.不取当前位置的物品:f(i, j, k, l) = f(i - 1, j, k, l) + f(i, j - 1, k, l);
这是显然的。
2.取当前位置的物品:再取之前,需要明确能不能,分成两组看
(1)如果l >= w(i, j),显然是不能的
(2)如果l < w(i, j),可以取,取的话就变成了对f(i, j, k + 1, w(i, j))进行操作,有点不方便,因为当前的f(i, j, k + 1, w(i, j))的结果是需要f(i, j, k, l)的铺垫,所以行不通
换一种思路:
之前是对i, j进行遍历,然后其余两个变量随缘,这显然是很不稳定的,无法进行下去也是应该的,现在我们对四个量同时进行遍历。
(1)还是先遍历坐标i, j;
(2)然后遍历个数l;
(3)最后遍历最大价值w(i, j);
现在再回过头去看卡住的地方:
就是该位置能不能取的问题
1.不取,同上面所分析的。
2.取,就要看什么时候才能取,当前最里面的一层是对l的遍历,所以要取的话需要满足以下情况
(1)手里有k - 1个物品
(2)手里的物品最大价值要低于w(i, j)
(3)取完后手里物品的最大价值是w(i, j)
现在就很明了了,如果要取(i,j)位置的物品需要满足的条件是w(i, j) == l ,计算的过程是
f(i, j, k, l) = f(i, j, k - 1, ## ),就是所有最大价值小于w(i, j),且手里有k - 1中物品的方案数
分析结果
题目要求的是出迷宫时,手里物品为k件的所有方案数,因此就是f(r, c, k, ##)(r, c,是出口的坐标)
题目就分析完了,上代码
#include<iostream>
using namespace std;
const int N = 55;
const int K = 14;
const int LL = 1e9 + 7;
int f[N][N][K][K];//f = ans, i = x, j = y, k = num, l = biggest;
int a[N][N];
int r, c;
int main()
{
int n;
int sum = 0;
cin >> r >> c >> n;
for(int i = 1; i <= r; i ++)
for(int j = 1; j <= c; j ++)
{
cin >> a[i][j];
a[i][j] ++;//需要注意
}
f[1][1][0][0] = 1;//在起始地址,不拿是一种方案
f[1][1][1][a[1][1]] = 1;//拿是一种方案
for(int i = 1; i <= r; i ++)//行
for(int j = 1; j <= c; j ++)//列
for(int k = 0; k <= n; k ++)//取的个数
for(int l = 0; l < K; l ++)//遍历最大值
{
f[i][j][k][l] = (f[i - 1][j][k][l] + f[i][j][k][l])% LL;
f[i][j][k][l] = (f[i][j][k][l] + f[i][j - 1][k][l])% LL;//不取
if(a[i][j] == l)//取
for(int s = 0; s < l; s ++)//就是所有在i,j的位置,且数量位k-1的所有情况的和
{
f[i][j][k][l] = (f[i][j][k][l] + f[i - 1][j][k - 1][s]) % LL;
f[i][j][k][l] = (f[i][j][k][l] + f[i][j - 1][k - 1][s]) % LL;
}
}
for(int i = 0; i < K; i ++)
sum = (sum + f[r][c][n][i])% LL;
cout << sum;
return 0;
}
注意:
1.开始遍历时,需要先给入口位置赋值,由于只有两种情况,所以手动赋值即可
f[1][1][0][0] = 1;//在起始地址,不拿是一种方案
f[1][1][1][a[1][1]] = 1;//拿是一种方案
2.由于这个题有个很神奇的地方:物品的价值可以为0,又由于这条语句
if(a[i][j] == l)//取
for(int s = 0; s < l; s ++)//就是所有在i,j的位置,且数量位k-1的所有情况的和
{
f[i][j][k][l] = (f[i][j][k][l] + f[i - 1][j][k - 1][s]) % LL;
f[i][j][k][l] = (f[i][j][k][l] + f[i][j - 1][k - 1][s]) % LL;
}
所以当数据为0时,即使选择了这个物品,f也不会更新,使得有一部分方案丢失,所以把每个数据都加1,是不会正确影响结果的
最后
拿个简单点的dp放松一下

浙公网安备 33010602011771号