原题链接:AcWing 1212. 地宫取宝 .
X 国王有一个地宫宝库,是 n×m 个格子的矩阵,每个格子放一件宝贝,每个宝贝贴着价值标签。
地宫的入口在左上角,出口在右下角。
小明被带到地宫的入口,国王要求他只能向右或向下行走。
走过某个格子时,如果那个格子中的宝贝价值比小明手中任意宝贝价值都大,小明就可以拿起它(当然,也可以不拿)。
当小明走到出口时,如果他手中的宝贝恰好是 k 件,则这些宝贝就可以送给小明。
请你帮小明算一算,在给定的局面下,他有多少种不同的行动方案能获得这 k 件宝贝。
输入格式
第一行 3 个整数,n,m,k,含义见题目描述。
接下来 n 行,每行有 m 个整数 Ci 用来描述宝库矩阵每个格子的宝贝价值。
输出格式
输出一个整数,表示正好取 k 个宝贝的行动方案数。
该数字可能很大,输出它对 1000000007 取模的结果。
数据范围
1≤n,m≤50,
1≤k≤12,
0≤Ci≤12
输入样例1:
2 2 2
1 2
2 1
输出样例1:
2
输入样例2:
2 3 2
1 2 3
2 1 5
输出样例2:
14
题目分析
状态表示
①小明要从入口到出口,每次只能向右或者向下走,类似于题目摘花生,可以用二维动态规划f[N][M]来解决。
②只有格子中的宝贝价值大于小明手中每个物品的价值才可以拿走,所有小明顺序拿的物品的价值一定是单调递增的,可以看成最长上升子序列问题的变形,而找上升子序列可以用到一维动态规划,故现在需要f[N][M][C],第三维保存上升序列中最后一个数。
③宝贝恰好k件才可以送给小明,故还需要一维来保存现在小明手中有多少件物品了。
最终有f[N][M][C][K],其中f[i][j][c][k]表示小明走到在位置(i, j)上手中最后一件物品价值为c, 一共有k件物品。
最终目标:寻找可行方案数
状态转移
位置(i, j)上的状态一定是由位置(i-1, j)和(i, j-1)转移过来的,所以位置(i, j)上的可行方案数是由位置(i-1, j)和(i, j-1)上的合法方案数之和。
①若小明 不选择 位置(i, j)上的物品t,则其可以由f[i-1][j][c][k]和f[i][j-1][c][k]转移到f[i][j][c][k]。
②若小明 选择 位置(i, j)上的物品t, 而只有在t>c而且小明手中物品小于K时才能选择,则其可以由f[i-1][j][c][k-1]和f[i][j-1][c][k-1]转移到f[i][j][c][k]。
于是有状态转移方程
因为选或是不选都是合法状态,所以两种状态要进行叠加。
边界,初值与结果
边界: 可以使i,j下标从1开始保证步越界
初值: 设c = INF(INF>12)时表示没有物品时的有效价值,则f[i][j][INF][0] = 1,f[i][j][c(1,1)][1] = 1其余点初值为0,有效方案均由这两个初始状态转化过去。
最终结果: 所有到达(n,m)的点且恰好拿到K件物品为方案数之和
注:中间数据又可能很大会爆int,需要临时转化成long long并且及时取模。
C++代码实现
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 55, M = 15, INF = 13, base = 1e9+7;
int f[N][N][M][M];//行,列,最后一个数,已经拿了几个数
int main() {
int n, m, k;
cin>>n>>m>>k;
for(int i = 1; i <= n; ++i) {
for(int j = 1; j<= m; ++j) {
int c;//第i,j物品的价值
cin>>c;
if(i == 1 && j == 1) {
//初值,其余点均由这两个状态转移过去
f[i][j][INF][0] = 1;
f[i][j][c][1] = 1;
continue;
}
for(int last = 0; last <= INF; ++last) {
for(int h = 0; h <= k; ++h) {
if((c > last || last == INF) && h > 0) {//拿
f[i][j][c][h] = ((LL)f[i][j][c][h] + f[i-1][j][last][h-1]+f[i][j-1][last][h-1])%base;
}
//不拿
f[i][j][last][h] = ((LL) f[i][j][last][h]+f[i-1][j][last][h]+f[i][j-1][last][h] )%base;
}
}
}
}
int ans = 0;
for(int i = 0; i < INF; ++i) ans = ((LL)ans+f[n][m][i][k])%base;
cout<<ans<<endl;
return 0;
}