4.4

JSCPC 省赛
线性 DP 与背包 DP 模板速查
铜牌目标 · 赛场可带 · C++ 模板版

一、DP 通用套路
遇到 DP 题,先按四步想:状态表示、状态转移、初始化、遍历顺序。
步骤 要问自己的问题 常见写法

  1. 状态表示 dp[i] 或 dp[i][j] 到底代表什么? 前 i 个、以 i 结尾、容量为 j
  2. 状态转移 当前状态从哪些旧状态转移来? max / min / 加法计数
  3. 初始化 dp[0]、dp[1]、边界怎么设? 0、INF、-INF、dp[0]=1
  4. 遍历顺序 是从前往后,还是容量倒序? 01 倒序,完全正序

赛场提醒
• 先判断题目是“选东西”还是“走路径”还是“处理序列”。
• 最大值用 max,最小值用 min,方案数通常用加法并注意取模。
二、模型识别速查表
题目关键词 优先模型 核心特征
每个物品最多选一次 01 背包 容量 j 倒序
每个物品可以无限选 完全背包 容量 j 正序
每个物品有数量限制 多重背包 二进制拆分转 01 背包
每组最多选一个 分组背包 每组内部互斥
刚好凑满 恰好装满背包 dp[0]=0,其余 -INF / INF
问多少种方案 背包方案数 dp[0]=1,转移用加法
不能选相邻 打家劫舍 dp[i]=max(dp[i-1],dp[i-2]+a[i])
连续子数组最大和 最大子段和 以 i 结尾,断开或延续
最长递增子序列 LIS 子序列不要求连续
两个字符串公共子序列 LCS 不要求连续
插入删除替换最少次数 编辑距离 字符串双序列 DP
网格向右向下走 路径 DP 从上方或左方转移

三、线性 DP 常用模板

  1. 爬楼梯 / 斐波那契模型
    题型特征:每次走 1 步或 2 步,问走到第 n 阶有多少种方案。
#include <bits/stdc++.h>
using namespace std;

int main() {
    int n;
    cin >> n;
    vector<long long> dp(n + 1);

    dp[0] = 1;
    if (n >= 1) dp[1] = 1;

    for (int i = 2; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }

    cout << dp[n] << '\n';
    return 0;
}
  1. 最大子段和
    题型特征:给一个数组,求连续子数组最大和。关键词是“连续”。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int main() {
    int n;
    cin >> n;
    vector<ll> a(n + 1), dp(n + 1);
    for (int i = 1; i <= n; i++) cin >> a[i];

    dp[1] = a[1];
    ll ans = dp[1];

    for (int i = 2; i <= n; i++) {
        dp[i] = max(a[i], dp[i - 1] + a[i]);
        ans = max(ans, dp[i]);
    }

    cout << ans << '\n';
    return 0;
}

核心理解
• dp[i] 表示以 a[i] 结尾的最大连续子段和。
• 要么从 a[i] 重新开始,要么接在前面的连续子段后面。
3. 打家劫舍 / 不能选相邻
题型特征:不能选相邻元素,求最大价值。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int main() {
    int n;
    cin >> n;
    vector<ll> a(n + 1), dp(n + 1);
    for (int i = 1; i <= n; i++) cin >> a[i];

    if (n == 0) {
        cout << 0 << '\n';
        return 0;
    }

    dp[0] = 0;
    dp[1] = a[1];

    for (int i = 2; i <= n; i++) {
        dp[i] = max(dp[i - 1], dp[i - 2] + a[i]);
    }

    cout << dp[n] << '\n';
    return 0;
}
  1. LIS 最长上升子序列
    题型特征:子序列不要求连续,要求严格递增或非递减。
    // O(n^2) 写法,适合 n <= 5000 左右
#include <bits/stdc++.h>
using namespace std;

int main() {
    int n;
    cin >> n;
    vector<int> a(n + 1), dp(n + 1, 1);
    for (int i = 1; i <= n; i++) cin >> a[i];

    int ans = 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);
            }
        }
        ans = max(ans, dp[i]);
    }

    cout << ans << '\n';
    return 0;
}

// O(n log n) 写法,适合 n 较大
#include <bits/stdc++.h>
using namespace std;

int main() {
    int n;
    cin >> n;
    vector<int> d;

    for (int i = 0; i < n; i++) {
        int x;
        cin >> x;
        auto it = lower_bound(d.begin(), d.end(), x); // 严格递增
        if (it == d.end()) d.push_back(x);
        else *it = x;
    }

    cout << d.size() << '\n';
    return 0;
}

严格递增与非递减
• 严格递增用 lower_bound。
• 非递减用 upper_bound。
5. LCS 最长公共子序列
题型特征:两个字符串,求最长公共子序列,不要求连续。

#include <bits/stdc++.h>
using namespace std;

int main() {
    string s, t;
    cin >> s >> t;
    int n = s.size(), m = t.size();

    vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (s[i - 1] == t[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            } else {
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }

    cout << dp[n][m] << '\n';
    return 0;
}
  1. 最长公共子串
    题型特征:两个字符串,求最长连续公共部分。注意和 LCS 区分:子串必须连续。
#include <bits/stdc++.h>
using namespace std;

int main() {
    string s, t;
    cin >> s >> t;
    int n = s.size(), m = t.size();
    vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));

    int ans = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (s[i - 1] == t[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
                ans = max(ans, dp[i][j]);
            }
        }
    }

    cout << ans << '\n';
    return 0;
}
  1. 编辑距离
    题型特征:两个字符串,可以插入、删除、替换,求最少操作次数。
#include <bits/stdc++.h>
using namespace std;

int main() {
    string s, t;
    cin >> s >> t;
    int n = s.size(), m = t.size();

    vector<vector<int>> dp(n + 1, vector<int>(m + 1));
    for (int i = 0; i <= n; i++) dp[i][0] = i;
    for (int j = 0; j <= m; j++) dp[0][j] = j;

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (s[i - 1] == t[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                dp[i][j] = min({
                    dp[i - 1][j] + 1,     // 删除
                    dp[i][j - 1] + 1,     // 插入
                    dp[i - 1][j - 1] + 1  // 替换
                });
            }
        }
    }

    cout << dp[n][m] << '\n';
    return 0;
}
  1. 数字三角形 DP
    题型特征:从三角形顶部走到底部,每次只能走左下或右下,求最大/最小路径和。
#include <bits/stdc++.h>
using namespace std;

int main() {
    int n;
    cin >> n;
    vector<vector<int>> a(n + 1, vector<int>(n + 1));
    vector<vector<int>> dp(n + 1, vector<int>(n + 1, -1e9));

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= i; j++) cin >> a[i][j];
    }

    dp[1][1] = a[1][1];
    for (int i = 2; i <= n; i++) {
        for (int j = 1; j <= i; j++) {
            dp[i][j] = max(dp[i - 1][j - 1], dp[i - 1][j]) + a[i][j];
        }
    }

    int ans = -1e9;
    for (int j = 1; j <= n; j++) ans = max(ans, dp[n][j]);
    cout << ans << '\n';
    return 0;
}

image

冶炼金属!!!!!
就是算公式,,,

1.image

2.判断平闰年?

3.怎么判断一些数字之前出现过了,或者判断一共出现过多少次不同的数字
image
image
image
image

3.2:
这里的.count(5)是什么意思呢?.count()函数是干什么的,怎么用
image
image
image
image

image

posted @ 2026-04-04 23:50  ervgiu28hg34y827  阅读(24)  评论(0)    收藏  举报