4.4
JSCPC 省赛
线性 DP 与背包 DP 模板速查
铜牌目标 · 赛场可带 · C++ 模板版
一、DP 通用套路
遇到 DP 题,先按四步想:状态表示、状态转移、初始化、遍历顺序。
步骤 要问自己的问题 常见写法
- 状态表示 dp[i] 或 dp[i][j] 到底代表什么? 前 i 个、以 i 结尾、容量为 j
- 状态转移 当前状态从哪些旧状态转移来? max / min / 加法计数
- 初始化 dp[0]、dp[1]、边界怎么设? 0、INF、-INF、dp[0]=1
- 遍历顺序 是从前往后,还是容量倒序? 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 步或 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;
}
- 最大子段和
题型特征:给一个数组,求连续子数组最大和。关键词是“连续”。
#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;
}
- 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;
}
- 最长公共子串
题型特征:两个字符串,求最长连续公共部分。注意和 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;
}
- 编辑距离
题型特征:两个字符串,可以插入、删除、替换,求最少操作次数。
#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;
}
- 数字三角形 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;
}

冶炼金属!!!!!
就是算公式,,,
1.
2.判断平闰年?
3.怎么判断一些数字之前出现过了,或者判断一共出现过多少次不同的数字




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





浙公网安备 33010602011771号