DP 类型题一 (模型:数字三角形+最长上升子序列+背包)
Posted 肆呀
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DP 类型题一 (模型:数字三角形+最长上升子序列+背包)相关的知识,希望对你有一定的参考价值。
DP 类型题一
- 一、数字三角形模型:只能朝右或下走
- 二、最长上升子序列模型
- 三、背包模型
- 1、 AcWing 532. 货币系统 :极大独立集
- 2、单调队列优化多重背包 O(NM)
- 3、AcWing 7. 混合背包问题
- 4、AcWing 8. 二维费用的背包问题
- 5、AcWing 1020. 潜水员:dp[i][j] 下标表示不小于
- 6、AcWing 1013. 机器分配 :求具体方案
- 7、AcWing 12. 背包问题求具体方案 :求字典序最小的具体方案
- 8、AcWing 11. 背包问题求方案数 :求最优解的方案数
- 9、AcWing 487. 金明的预算方案 :二进制枚举构造分组背包的决策
- 10、AcWing 10. 有依赖的背包问题 :树形套分组
- 11、AcWing 734. 能量石 :贪心+变种01
一、数字三角形模型:只能朝右或下走
1、AcWing 1018. 最低通行费:只走一次
//只从左边或者上面过来
#include <bits/stdc++.h>
using namespace std;
#define getlen(array) {return (sizeof(array) / sizeof(array[0]));}
#define ll long long
#define ull unsigned long long
#define PII pair<int, int>
#define PLL pair<ll, ll>
#define MEM(x, y) memset(x, y, sizeof x)
#define rin int n; scanf("%d", &n)
#define rln ll n; scanf("%lld", &n)
#define rim int m; scanf("%d", &m)
#define rit int t; scanf("%d", &t)
#define ria int a; scanf("%d", &a)
#define sc scanf
#define pr printf
const int INF = 0x3f3f3f3f;
const int N = 110;
int dx[] = {-1, 1, 0, 0}, dy[] = {0, 0, -1, 1};
int nums[N][N];
int main() {
//freopen("D:\\\\in.txt", "r", stdin);
rin;
MEM(nums, INF); //需要把临边改为无穷大,否则诸如nums[1][2]会一直等于本身
nums[0][1] = 0; //同理不让第一个点硬加上INF
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= n; ++ j) {
cin >> nums[i][j];
nums[i][j] += min(nums[i - 1][j], nums[i][j - 1]);
}
}
cout << nums[n][n];
return 0;
}
2、AcWing 1027. 方格取数:走两次,只拿一次,且可重复
/*
k - 当前位置横纵坐标之和
i1 - 第一条路线走到的横坐标
i2 - 第二条路线走到的横坐标
*/
#include<bits/stdc++.h>
using namespace std;
const int N = 20;
int nums[N][N];
int dp[2 * N][N][N];
int main() {
int n, r, c, num;
cin >> n;
while (cin >> r >> c >> num, r | c | num) {
nums[r][c] = num;
}
for (int k = 2; k <= 2 * n; ++ k) {
for (int i1 = 1; i1 <= n; ++ i1) {
for (int i2 = 1; i2 <= n; ++ i2) {
int j1 = k - i1, j2 = k - i2;
if (j1 >= 1 && j1 <= n && j2 >= 1 && j2 <= n) { //位置的合法性
int t = nums[i1][j1];
if (i1 != i2 && j1 != j2) t += nums[i2][j2]; //两个点若是同一个,不可以重复计算
int & v = dp[k][i1][i2];
v = max(v, dp[k - 1][i1 - 1][i2 - 1] + t);
v = max(v, dp[k - 1][i1 - 1][i2] + t);
v = max(v, dp[k - 1][i1][i2 - 1] + t);
v = max(v, dp[k - 1][i1][i2] + t);
}
}
}
}
cout << dp[2 * n][n][n];
return 0;
}
3、AcWing 275. 传纸条:走两次,只拿一次,且不可重复
【解法一】
其实这一题和上一题的取方格数可以是完全一样的代码 ac 掉,在下面的传送门有详细说明。
证明最终结果一定可以不交叉又是最大值
【解法二】
在重复点处进行特判,如下代码注释:
#include<bits/stdc++.h>
using namespace std;
const int N = 60;
int nums[N][N];
int dp[2 * N][N][N];
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= m; ++ j) {
cin >> nums[i][j];
}
}
for (int k = 2; k <= n + m; ++ k) {
for (int i1 = 1; i1 <= n; ++ i1) {
for (int i2 = 1; i2 <= n; ++ i2) {
int j1 = k - i1, j2 = k - i2;
if (j1 >= 1 && j1 <= m && j2 >= 1 && j2 <= m) {
//除了起点和终点外的点不可以重复走,若重复了,说明该方案不可行,设为无穷小
if (i1 == i2 && j1 == j2 && k != 2 && k != n + m) {
dp[k][i1][i2] = -0x3f3f3f3f;
continue;
}
int t = nums[i1][j1];
if (i1 != i2 && j1 != j2) t += nums[i2][j2];
int &v = dp[k][i1][i2];
v = max(v, dp[k - 1][i1 - 1][i2 - 1] + t);
v = max(v, dp[k - 1][i1 - 1][i2] + t);
v = max(v, dp[k - 1][i1][i2 - 1] + t);
v = max(v, dp[k - 1][i1][i2] + t);
}
}
}
}
cout << dp[n + m][n][n];
return 0;
}
二、最长上升子序列模型
1、AcWing 1014. 登山:既上山又下山
原题链接:https://www.acwing.com/problem/content/1016/
/*
旅程其实是包括上山和下山的,下山也是可以看景点的……
所以就是求从山脚到位置 i 和 i 到山脚过程中,满足游玩景点的编号符合从小到大即可
*/
#include <bits/stdc++.h>
using namespace std;
#define getlen(array) {return (sizeof(array) / sizeof(array[0]));}
#define ll long long
#define ull unsigned long long
#define PII pair<int, int>
#define PLL pair<ll, ll>
#define MEM(x, y) memset(x, y, sizeof x)
#define rin int n; scanf("%d", &n)
#define rln ll n; scanf("%lld", &n)
#define rim int m; scanf("%d", &m)
#define rit int t; scanf("%d", &t)
#define ria int a; scanf("%d", &a)
#define sc scanf
#define pr printf
const int INF = 0x3f3f3f3f;
const int N = 1010;
int dx[] = {-1, 1, 0, 0}, dy[] = {0, 0, -1, 1};
int up[N], down[N], nums[N];
int main() {
//freopen("D:\\\\in.txt", "r", stdin);
int n;
cin >> n;
nums[0] = nums[n + 1] = -INF;
for (int i = 1; i <= n; ++ i) cin >> nums[i];
int ans = 0;
//up[i]:从 1 走到 i 的最长上升子序列
for (int i = 1; i <= n; ++ i) {
for (int j = 0; j < i; ++ j) {
if (nums[j] < nums[i]) up[i] = max(up[i], up[j] + 1);
}
}
//down[i]:从 n 走到 i 的最长上升子序列 == 从 i 走到 n 的最长下降子序列
for (int i = n; i >= 1; -- i) {
for (int j = n + 1; j > i; -- j) {
if (nums[i] > nums[j]) down[i] = max(down[i], down[j] + 1);
}
}
for (int i = 1; i <= n; ++ i) ans = max(ans, up[i] + down[i] - 1);
cout << ans;
return 0;
}
2、AcWing 1012. 友好城市:线性互不交叉
原题链接:https://www.acwing.com/problem/content/1014/
/*
在按照如下规则排序后,最大不交叉数目就是最长上升子序列的元素数目
因为要想不交叉,那么第 i + 1 个北岸城市在南岸的友好城市的位置必须比第 i 个的大。
抽象出来就是最长上升子序列,只不过多了对北岸城市正序排序的操作。
*/
#include<bits/stdc++.h>
using namespace std;
const int N = 5010;
struct node{
int up, down; //分别表示北岸和南岸的位置
}arr[N];
//按照北岸的位置从左到右排序
bool cmp(node a, node b) {
return a.up < b.up;
}
int dp[N];
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; ++ i) {
int a, b;
cin >> a >> b;
arr[i] = {a, b};
}
sort(arr + 1, arr + n + 1, cmp);
int ans = 0;
for (int i = 1; i <= n; ++ i) {
for (int j = 0; j < i; ++ j) {
if (arr[j].down < arr[i].down) dp[i] = max(dp[i], dp[j] + 1);
}
ans = max(ans, dp[i]);
}
cout << ans;
return 0;
}
3、AcWing 1016. 最大上升子序列和
原题链接:https://www.acwing.com/problem/content/1018/
/*
模式和最长上升子序列是一样的,不同在于,每一次在前面找到可以接的位置
时,是判断数值而不是个数。
*/
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int nums[N], dp[N];
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; ++ i) cin >> nums[i];
int ans = 0;
for (int i = 1; i <= n; ++ i) {
dp[i] = nums[i];
for (int j = 1; j < i; 以上是关于DP 类型题一 (模型:数字三角形+最长上升子序列+背包)的主要内容,如果未能解决你的问题,请参考以下文章