题目
X 国王有一个地宫宝库。是 n × m n \times m n×m 个格子的矩阵。每个格子放一件宝贝。每个宝贝贴着价值标签。
地宫的入口在左上角,出口在右下角。
小明被带到地宫的入口,国王要求他只能向右或向下行走。
走过某个格子时,如果那个格子中的宝贝价值比小明手中任意宝贝价值都大,小明就可以拿起它(当然,也可以不拿)。
当小明走到出口时,如果他手中的宝贝恰好是k件,则这些宝贝就可以送给小明。
请你帮小明算一算,在给定的局面下,他有多少种不同的行动方案能获得这 k k k 件宝贝。
输入格式
输入一行 3 个整数,用空格分开: n n n m m m k k k
接下来有 n n n 行数据,每行有 m m m 个整数 C i ( 0 < = C i < = 12 ) C_i (0 <= C_i <= 12) Ci(0<=Ci<=12) 代表这个格子上的宝物的价值
输出格式
要求输出一个整数,表示正好取 k k k 个宝贝的行动方案数。该数字可能很大,输出它对 1000000007 1000000007 1000000007 取模的结果。
数据范围
1 < = n , m < = 50 , 1 < = k < = 12 1<=n, m<=50, 1<=k<=12 1<=n,m<=50,1<=k<=12
输入输出样例
样例输入1
2 2 2
1 2
2 1
样例输出1
2
样例输入2
2 3 2
1 2 3
2 1 5
样例输出2
14
思路
DP。给定一个 n × m n \times m n×m 图,图中每个点都有一个不同价值的物品,从左上角出发到右下角,每次只能向右或向下行一步,对所到达的点上的物品,若当前点的物品大于任意一个已取得的物品,可以选择拿或不拿。求到达右下角时,恰好拿到k个物品的方案数。
因为题目中要求了每次拿的物品价值必须是递增的,所以当前手上所有物品中价值最大的一定是在最后一个点处取得的物品,且后面拿的物品一定比前一个的物品价值大。
状态表示:我们用f[i][j][u][v]
表示在点(i,j)已取得 u 个物品,且其中当前手上物品中的最大价值为 v。
状态计算:按照最后一步(即点(i,j)处)的取法可分为四种情况:
- 从左向右走至点(i,j),且不拿该点的物品,方案数为
f[i][j][u][v] + f[i - 1][j][u][v]
- 从上到下走至点(i,j),且不拿该点的物品,方案数为
f[i][j][u][v] + f[i][j - 1][u][v]
- 从左向右走至点(i,j),且拿该点的物品,方案数为
f[i][j][u][v] + f[i - 1][j][u - 1][1 ~ v - 1]
- 从上向下走至点(i,j),且拿该点的物品,方案数为
f[i][j][u][v] + f[i][j - 1][u - 1][1 ~ v - 1]
(考虑到不要让拿的物品数超过k个)
+1偏移量,因为价值
C
i
(
0
<
=
C
i
<
=
12
)
C_i (0 <= C_i <= 12)
Ci(0<=Ci<=12) ,而我们一开始不选择的时候f[i][j][u][v],此时v存在不选择的情况,则我们可以填写比0小的数字-1;
但因为-1无法做数组下标,直接对所有价值全部加1,就变成了1到13,原有的0就可以作为不选择的下标了。
初始化边界条件,在(1,1)点处有两种情况——拿或不拿,其实对于这两种情况的方案数都是1。如果不拿,那么就是获得0个物品这1种方案;如果拿了,那么就是只能获得(1,1)点处的物品,当前最大价值为map[1][1]
这1种方案。即f[1][1][0][0] = f[1][1][1][map[1][1]] = 1;
。
因为每次只能不断拿比之前价值更大的物品,所以假如这次拿了的话,这次拿的物品一定是最大的,因此只能是v==g[i][j]
的时候才符合这个意义。
举个反例,假如x!=a[i][j]
,f[i][j][u][x]
表示:在
(
i
,
j
)
(i,j)
(i,j) 这个点,拿了
u
u
u 个物品,这些物品中价值最大的是
x
x
x,但
(
x
,
y
)
(x,y)
(x,y) 这个点的物品的价值为g[i][j]
才对,这是不符合一开始的数组定义的。
代码
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 60;
const int K = 20;
const int MOD = 1e9 + 7;
int n, m, k, res, g[N][N], f[N][N][K][K];
int main() {
scanf("%d%d%d", &n, &m, &k);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++) {
scanf("%d", &g[i][j]);
g[i][j]++;//+1偏移量
}
f[1][1][0][0] = f[1][1][1][g[1][1]] = 1;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
for(int u = 0; u <= k; u++)
for(int v = 0; v < K; v++) {
f[i][j][u][v] = (f[i][j][u][v] + f[i - 1][j][u][v]) % MOD;
f[i][j][u][v] = (f[i][j][u][v] + f[i][j - 1][u][v]) % MOD;
if(u > 0 && v == g[i][j]) {
for(int s = 0; s < g[i][j]; s++) {
f[i][j][u][v] = (f[i][j][u][v] + f[i - 1][j][u - 1][s]) % MOD;
f[i][j][u][v] = (f[i][j][u][v] + f[i][j - 1][u - 1][s]) % MOD;
}
}
}
for(int i = 1; i < K; i++) //最后把在点(n,m)处拿k个物品的方案数累加起来。
res = (res + f[n][m][k][i]) % MOD;
printf("%d", res);
return 0;
}