动态规划dp

 Count on Me - Connie Talbot - 单曲 - 网易云音乐

目录

前言

01背包

摘花生

最长上升子序列

地宫取宝

AC  DP

AC  记忆化搜索

波动数列


个人认为,打表检查是最重要的一步,可以有效提高正确率

前言

结合动规5部曲,套模板速刷,关于动规5部曲

一,确定 dp[i] 或者 dp[i][j] 中 dp[i] 和 i( dp[i][j] 和 i ,  j )的具体含义(状态),比如斐波那契数列中,dp[i] 表示第 i 个斐波那契数,i 表示第几个

二,找递推公式(通过最后一步,将问题转化成子问题

三,如何初始化(你要初始化多少个(哪一行哪一列...),初始化的值是什么,同时避免越界)

四,确定遍历顺序(一维前往后后往前;二维从左上角,右下角,甚至是中间等开始遍历)

五,打印dp数组(cout或者printf的形式debug)-> 观察结果

01背包

2. 01背包问题 - AcWing题库

AC  代码1

含详细注释                    二维dp

#include
using namespace std;

const int N = 1005;

int main()
{
    int n, m, w[N] = {0}, v[N] = {0}, dp[N][N];
    cin>>n>>m;
    for (int i = 1; i <= n; ++i)
        cin>>w[i]>>v[i]; // 读入重量weight和价值value
    // 
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j) {
            dp[i][j] = dp[i - 1][j]; // 不放
            if (j >= w[i]) // 可以放
                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
        }
    
    cout<

摘花生

1015. 摘花生 - AcWing题库

AC  代码

/*
dp[i][j] 走到i, j时最多花生数
dp[i][j] = max(dp[i - 1][j] + a[i][j], dp[i][j - 1] + a[i][j]);
西北角是(1, 1), 不用初始化
左上角遍历到右下角
打表检查
*/
#include
using namespace std;

int main()
{
    int t, r, c;
    int dp[110][110] = {0}, a[110][110] = {0};
    cin>>t;
    while (t--) {
        cin>>r>>c;
        
        for (int i = 1; i <= r; ++i)
            for (int j = 1; j <= c; ++j) {
                cin>>a[i][j];
                dp[i][j] = max(dp[i - 1][j] + a[i][j], dp[i][j - 1] + a[i][j]);
            }
        cout<

最长上升子序列

活动 - AcWing

AC  代码

解释在注释中

/*
dp[i] 第i个数结尾的最大长度
dp[i] = max(dp[j] + 1, dp[i])
每个数自成1个序列,初始化为1
从1开始遍历
打表检查
*/
#include
#include
using namespace std;

int main()
{
    long long n, a[1010] = {0}, dp[1010] = {0};
    cin>>n;
    for (int i = 1; i <= n; ++i) {
        cin>>a[i];
        dp[i] = 1; // 初始化   
    }
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j < i; ++j) 
            if (a[j] < a[i]) // 满足上升条件
                dp[i] = max(dp[i], dp[j] + 1);
    }
    // 因为是第i个结尾, 遍历一遍找最大值
    long long ans = 0;
    for (int i = 1; i <= n; ++i)
        ans = max(ans, dp[i]);
    cout<

地宫取宝

1212. 地宫取宝 - AcWing题库

一道结合了摘花生和最长上升子序列的题

4维dp,即dp[][][][]

思路

动态规划dp_第1张图片

动态规划dp_第2张图片

首先明确一点,题目要求的是方案数,dp[i][j][k][c]时,坐标(i, j)处的方案数,来自于上方

取 / 不取,左方取 / 不取,共4种情况的和

考虑到mod 1e9 + 7,最多2个相加就要取余(防止爆int)  

AC  DP

初始化中,为了防止下标 -1, 读入v[][],value时,需要++,那么后续遍历最大值时,最大就不是12,而是13了,要细心

/*
(1)含义
dp[i][j][k][c] 表示走到(i, j)时, 已选了k个数, 且最后一个数(即当前的数, 也是最大的数)是c, 的总方案数
(2)递推式
dp[i][j][k][c] = dp[i - 1][j][k][c] + dp[i][j - 1][k][c]; if判断再 + dp[i - 1][j][k - 1][c'] + dp[i][j - 1][k - 1][c'']
(3)初始化
只需对起点初始化, dp[1][1][1][v[i][j]]选, dp[1][1][0][-1]不选, 下标不为-1所以全部 +1
(4)遍历顺序
左上角到右下角
(5)打表debug
*/
#include
using namespace std;

const int N = 55, mod = 1000000007;

int main()
{
    int n, m, k, dp[N][N][13][14] = {0}, v[N][N]= {0};
    cin>>n>>m>>k;
    
    // 读入每件物品价值
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j) {
            cin >> v[i][j];
            v[i][j]++; // dp[][][][c], 保证c == 0时, 表示未选
        }
        
    // 初始化起点
    dp[1][1][1][v[1][1]] = 1; // 选
    dp[1][1][0][0] = 1; //不选
    
    // 5层for
    for (int i = 1; i <= n; ++i) // 横坐标
        for (int j = 1; j <= m; ++j) { // 纵坐标
            if (i == 1 && j == 1) continue;
            for (int o = 0; o <= k; ++o) // 当前个数o
                for (int t = 0; t <= 13; ++t) { // 当前最大值t, 0 ~ 13
                    int &var = dp[i][j][o][t]; 
                    // 加上左边, 上边, 不取的2个方案数
                    var = (var + dp[i - 1][j][o][t]) % mod;
                    var = (var + dp[i][j - 1][o][t]) % mod;
                    // 先判断, 再加上取得2个方案数
                    if (o > 0 && v[i][j] == t) { // 要取的话, 当前个数至少为1
                        // 枚举 c'
                        for (int ct = 0; ct < t; ++ct) {
                            var = (var + dp[i - 1][j][o - 1][ct]) % mod; // 取之前已取o - 1, 最大值ct, ct == 0表示未取
                            var = (var + dp[i][j - 1][o - 1][ct]) % mod;
                        }
                    }
                }
        }
        
    // 遍历当前最大值, 所有方案数相加
    int ans = 0;
    for (int i = 1; i <= 13; ++i) // 考虑到, 读入时的++, 所以是1~13
        ans = (ans + dp[n][m][k][i]) % mod;
    cout<

AC  记忆化搜索

关于为什么能把-1传到dp[][][][]作为下标索引,这点,不是很理解,也许程序会自动识别为无效索引

#include
#include // memset()
using namespace std;

int n, m, k;
const int N = 55, mod = 1000000007;
int dp[N][N][13][14], v[N][N];

int dfs(int x, int y, int step, int w)
{
    int &var = dp[x][y][step][w]; // 引用当前状态方案数
    if (var >= 0) return var; // 记忆化, 已计算的直接返回, 避免重复计算
    if (x > n || y > m || step > k) return 0; // 越界
    
    // 判断边界
    if (x == n && y == m) {
        if (step == k || (step == k - 1 && v[x][y] > w))
            return 1; // 满足一种方案的条件
        return 0; // 不满足
    }
    
    // 枚举4种情况
    var = 0; // 保证每条路径的独立
    var = (var + dfs(x, y + 1, step, w)) % mod; // 左方, 不取
    var = (var + dfs(x + 1, y, step, w)) % mod; // 下方, 不取
    
    if (v[x][y] > w) {
        var = (var + dfs(x + 1, y, step + 1, v[x][y])) % mod; // 下方, 取
        var = (var + dfs(x, y + 1, step + 1, v[x][y])) % mod; // 左方, 取
    }
    
    return var;
}

int main()
{
    cin>>n>>m>>k;
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j)
            cin>>v[i][j];
    memset(dp, -1, sizeof dp); // 初始化方案数都为 -1, 表示未计算过
    
    cout<

波动数列

题目

1214. 波动数列 - AcWing题库

视频

AcWing 1214. 波动数列(蓝桥杯C++ AB组辅导课) - AcWing

题解

波动数列 - onlyblues - 博客园 (cnblogs.com)

代码很短,难点在于dp[][]含义和递推式的分析,本质是组合问题

AC  代码

/*
(1)含义
dp[i][j], 只考虑前 i 项, 当前的和模 n 余 j
(2)递推式
dp[i][j] = ( dp[i - 1][get_mod(j - i*a, n) + dp[i - 1][get_mod(j + i*b, n) ) % mod;
(3)初始化
dp[0][0] = 1, 前0项, 和为0模n余0
*/
#include
using namespace std;

const int N = 1010, mod = 100000007;
int dp[N][N];

int get_mod(int a, int b)
{
    return (a % b + b) % b; // 返回正余数
}

int main()
{
    int n, s, a, b;
    cin>>n>>s>>a>>b;
    dp[0][0] = 1; 
    for (int i = 1; i < n; ++i) // 前 i 项
        for (int j = 0; j < n; ++j)  // 模n的余数j
            dp[i][j] = ( dp[i - 1][get_mod(j - i*a, n)] + dp[i - 1][get_mod(j + i*b, n)] ) % mod;
    cout<

你可能感兴趣的:(2024蓝桥杯备赛,动态规划,算法)