AcWing 1212. 地宫取宝 动态规划实现,详细分析

原题链接:AcWing 1212. 地宫取宝 .
X 国王有一个地宫宝库,是 n×m 个格子的矩阵,每个格子放一件宝贝,每个宝贝贴着价值标签。

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

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

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

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

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

输入格式
第一行 3 个整数,n,m,k,含义见题目描述。

接下来 n 行,每行有 m 个整数 Ci 用来描述宝库矩阵每个格子的宝贝价值。

输出格式
输出一个整数,表示正好取 k 个宝贝的行动方案数。

扫描二维码关注公众号,回复: 11630994 查看本文章

该数字可能很大,输出它对 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]。
于是有状态转移方程
f [ i ] [ j ] [ c ] [ k ] = { f [ i 1 ] [ j ] [ c ] [ k ] + f [ i ] [ j 1 ] [ c ] [ k ] f [ i 1 ] [ j ] [ c ] [ k 1 ] + f [ i ] [ j 1 ] [ c ] [ k 1 ] t > c f[i][j][c][k]=\left\{ \begin{aligned} f[i-1][j][c][k]+f[i][j-1][c][k] \\ f[i-1][j][c][k-1]+f[i][j-1][c][k-1], t>c \end{aligned} \right.
因为选或是不选都是合法状态,所以两种状态要进行叠加。

边界,初值与结果

边界: 可以使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;
}

猜你喜欢

转载自blog.csdn.net/mwl000000/article/details/108243202