题目V^V
一、回顾dp背包
1. 0-1背包问题
问题描述:给定n个物品,每个物品有重量w[i]和价值v[i],背包容量为W。每个物品只能选或不选,求最大价值。
状态转移方程:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i])
C++实现:
int knapsack01(vector<int>& w, vector<int>& v, int W) {int n = w.size();vector<vector<int>> dp(n+1, vector<int>(W+1, 0));for (int i = 1; i <= n; ++i) {for (int j = 1; j <= W; ++j) {if (j >= w[i-1]) {dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i-1]] + v[i-1]);} else {dp[i][j] = dp[i-1][j];}}}return dp[n][W]; }// 空间优化版(一维数组) int knapsack01_opt(vector<int>& w, vector<int>& v, int W) {vector<int> dp(W+1, 0);for (int i = 0; i < w.size(); ++i) {for (int j = W; j >= w[i]; --j) { // 逆序更新dp[j] = max(dp[j], dp[j-w[i]] + v[i]);}}return dp[W]; }
2. 完全背包问题
问题描述:与0-1背包类似,但每个物品可以选无限次。
状态转移方程:
dp[i][j] = max(dp[i-1][j], dp[i][j-w[i]] + v[i])
C++实现:
int completeKnapsack(vector<int>& w, vector<int>& v, int W) {int n = w.size();vector<int> dp(W+1, 0);for (int i = 0; i < n; ++i) {for (int j = w[i]; j <= W; ++j) { // 正序更新dp[j] = max(dp[j], dp[j-w[i]] + v[i]);}}return dp[W]; }
3. 多重背包问题
问题描述:与0-1背包类似,但每个物品有数量限制s[i]。
解法:可以转化为0-1背包问题(二进制优化)
C++实现:
int multipleKnapsack(vector<int>& w, vector<int>& v, vector<int>& s, int W) {vector<int> dp(W+1, 0);for (int i = 0; i < w.size(); ++i) {// 二进制优化拆分int num = min(s[i], W / w[i]);for (int k = 1; num > 0; k <<= 1) {if (k > num) k = num;num -= k;for (int j = W; j >= w[i] * k; --j) {dp[j] = max(dp[j], dp[j - w[i]*k] + v[i]*k);}}}return dp[W]; }
4. 分组背包问题
问题描述:物品被分为若干组,每组中只能选一个物品。
C++实现:
int groupKnapsack(vector<vector<int>>& groups_w, vector<vector<int>>& groups_v, int W) {vector<int> dp(W+1, 0);for (int k = 0; k < groups_w.size(); ++k) { // 遍历每组for (int j = W; j >= 0; --j) { // 逆序for (int i = 0; i < groups_w[k].size(); ++i) { // 遍历组内物品if (j >= groups_w[k][i]) {dp[j] = max(dp[j], dp[j - groups_w[k][i]] + groups_v[k][i]);}}}}return dp[W]; }
5. 背包问题变种
5.1 恰好装满背包
初始化时dp[0] = 0
,其他为-INF
(求最大价值)或INF
(求最小价值)
5.2 方案数问题
状态转移改为累加方案数
int countWays(vector<int>& w, int W) {vector<int> dp(W+1, 0);dp[0] = 1;for (int i = 0; i < w.size(); ++i) {for (int j = W; j >= w[i]; --j) {dp[j] += dp[j - w[i]];}}return dp[W]; }
5.3 二维费用背包
增加一维状态表示第二种限制
int twoDimensionalKnapsack(vector<int>& w1, vector<int>& w2, vector<int>& v, int W1, int W2) {vector<vector<int>> dp(W1+1, vector<int>(W2+1, 0));for (int k = 0; k < w1.size(); ++k) {for (int i = W1; i >= w1[k]; --i) {for (int j = W2; j >= w2[k]; --j) {dp[i][j] = max(dp[i][j], dp[i-w1[k]][j-w2[k]] + v[k]);}}}return dp[W1][W2]; }
总结
-
0-1背包:物品只能选一次,内层循环逆序
-
完全背包:物品可选无限次,内层循环正序
-
多重背包:物品有数量限制,可二进制优化
-
分组背包:每组只能选一个物品,最内层循环组内物品
-
变种问题:注意初始化条件和状态转移含义的变化
掌握这些基本模型后,大多数背包问题都能通过适当变形解决。
二、题
1、P2196 [NOIP 1996 提高组] 挖地雷 - 洛谷
解题思路:建图,dp[i]表示为以i结尾的最大的地雷数,于是就有了
#include <bits/stdc++.h>
#define ll long long
#define pii pair<int,int>
#define endl "\n"
const int MOD = 1e9 + 7;
using namespace std;int main() {int n; cin >> n;vector<int>a(n + 1);vector<int>dp(21, 0); // 表示以i点结束的最大结果vector<vector<int>>graph(n + 1);vector<vector<int>>way(n + 1);for (int i = 1; i <= n; i++)cin >> a[i];int temp;for (int i = 1; i < n; i++) {for (int j = i + 1; j <= n; j++) {cin >> temp;if (temp) {graph[i].push_back(j);}}}for (int i = 1; i <= n; i++)dp[i] = a[i], way[i].push_back(i);for (int i = 1; i <= n; i++) {for (auto j : graph[i]) {if (dp[j] < dp[i] + a[j]) {dp[j] = dp[i] + a[j];way[j] = way[i]; way[j].push_back(j);}}}int mx = 1;for (int i = 1; i <= n; i++) {mx = dp[mx] > dp[i] ? mx : i;}for (auto i : way[mx])cout << i << " ";cout << endl << dp[mx];return 0;
}
2、P4017 最大食物链计数 - 洛谷
解题思路:这个题目,我用找规律写的,如果一个物种,没用吃的,那就是生产者,如果一个物种没有被吃的物种,就是顶级物种,然后对生产者初始化为1,dp[i]+=dp[j],i是j的天敌既有了
#include <bits/stdc++.h>
#define ll long long
#define pii pair<int,int>
#define endl "\n"
using namespace std;
const int MOD = 80112002;int main() {int n, m; cin >> n >> m;vector<vector<int>> graph(n + 1);vector<int> in_degree(n + 1, 0);vector<int> out_degree(n + 1, 0);vector<int> dp(n + 1, 0);for (int i = 0; i < m; i++) {int a, b;cin >> a >> b;graph[a].push_back(b);out_degree[a]++;in_degree[b]++;}queue<int> q;for (int i = 1; i <= n; i++) {if (in_degree[i] == 0) {dp[i] = 1;q.push(i);}}while (!q.empty()) {int u = q.front();q.pop();for (int v : graph[u]) {dp[v] = (dp[v] + dp[u]) % MOD; in_degree[v]--;if (in_degree[v] == 0) {q.push(v);}}}int ans = 0;for (int i = 1; i <= n; i++) {if (out_degree[i] == 0) {ans = (ans + dp[i]) % MOD;}}cout << ans << endl;return 0;
}
3、P1216 [IOI 1994] 数字三角形 Number Triangles - 洛谷
解题思路:毫无疑问dp[i][j]表示当前位置的最大值dp[i][j] = max( dp[i-1][j-1],dp[i-1][j] ) + nums[i][j]
#include <bits/stdc++.h>
#define ll long long
#define pii pair<int,int>
#define endl "\n"
using namespace std;int main() {ios::sync_with_stdio(0);cout.tie(0); cin.tie(0);int r; cin >> r;vector<vector<int>>nums(r + 1, vector<int>(r + 2, 0));vector<vector<int>>dp(r + 1, vector<int>(r + 1, 0));for (int i = 1; i <= r; i++) for (int j = 1; j <= i; j++) cin >> nums[i][j];int ans = 0;for (int i = 1; i <= r; i++) {for (int j = 1; j <= i; j++) {dp[i][j] = max(dp[i - 1][j - 1], dp[i - 1][j]) + nums[i][j];if (i == r)ans = max(dp[i][j], ans);}}cout << ans;return 0;
}