DP 类型题一 (模型:数字三角形+最长上升子序列+背包)

Posted 肆呀

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DP 类型题一 (模型:数字三角形+最长上升子序列+背包)相关的知识,希望对你有一定的参考价值。

一、数字三角形模型:只能朝右或下走

1、AcWing 1018. 最低通行费:只走一次

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. 方格取数:走两次,只拿一次,且可重复

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. 传纸条:走两次,只拿一次,且不可重复

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 类型题一 (模型:数字三角形+最长上升子序列+背包)的主要内容,如果未能解决你的问题,请参考以下文章

DP 类型题一 (模型:数字三角形+最长上升子序列+背包)

动态规划之最长上升子序列模型

动态规划之最长上升子序列模型

动态规划线性dp问题总结:数字三角形最长上升子序列最长公共子序列最短编辑距离 题解与模板

数字三角形模型和最长上升子序列模型 AcWing题目

算法基础课题目时间