dp 笔记

dp 笔记

背包类型

01 背包

这种类型的典型问题就是给定一个总数,再给定 n n n 个物品,每个物品都是消耗一定的数值得到一定的数值。

这种问题最开始会想到用贪心解决。

但是有时候贪心为了拿最高的,舍掉了两个次高的,但恰好两个次高的和比最高的更大,这样就亏大了。

动态规划,就是从小的往大的推,我们不妨设 dp[i][j] = 如果有 i 个物品;总数,也就是背包体积为 j,最佳答案是多少。

那么对于每个物品有两种状态,选 和 不选。我们肯定是选两种中最优的方法,那么当可以选的时候,可以用 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 消耗] + 价值)

这样已经很好了,但是空间还是不够优。

我们可以试着让这个数组降一维。我们发现 dp 数组的第一维,都是从 i − 1 i - 1 i1 转移过来的,我们可以直接忽略掉第一维,但是需要注意到是因为如果我们的第二层循环是正的,那么 dp[i][j] 就会从 dp[i][] 传过来,就不是从 dp[i - 1][] 传过来,这样的话一个物品就可以拿无限次了,就变成了完全背包。我们的循环要倒着。

为了免掉判断,我们的第二层循环可以直接到这个物品的消耗就停,毕竟后面的肯定是无法拿。

这样的时间复杂度是 O(n(V-w[i]))

空间复杂度是 O(V)
例题+代码

采药

#include <bits/stdc++.h>
using namespace std;
int main() {
	ios::sync_with_stdio(false);
	int dp[10005] = {0}, n, V, v, w;
	cin >> V >> n;
	for (int i = 1; i <= n; i++) {
		cin >> w >> v;
		for (int j = V; j >= w; j--) {
			dp[j] = max(dp[j], dp[j - w] + v);
		}
	} 
	cout << dp[V];
	return 0;
}

完全背包

完全背包和 01 背包的最大的不同就是一个物品可以拿多次。

在两维 01 背包中的第一个维度就是控制一个物品只能那一次,不管要不要去都是从上一个传承下来的。但是完全背包的这一维就可以直接从自己传承来,也就是可以无限重复拿。

既然这样,我们可以直接把这意味搞掉,而不用对循环进行操作。

大部分背包都是从 01 背包改下来的,我们做背包题目的时候,只要发现背包的几要素,就可以轻而易举的解决了。

#include <bits/stdc++.h>
using namespace std;
int main() {
	ios::sync_with_stdio(false);
	int dp[10005] = {0}, n, V, v, w;
	cin >> V >> n;
	for (int i = 1; i <= n; i++) {
		cin >> w >> v;
		for (int j = w; j <= V; j++) {
			dp[j] = max(dp[j], dp[j - w] + v);
		}
	} 
	cout << dp[V];
	return 0;
}

分组背包

讲完完全背包,我们来看一下分组背包。

这种背包其实就是 01 背包加一个分组(废话)。

做这种题目时很容易想到要在 01 背包的循环上面套一个组别的循环,但是顺序可有大讲究。

这种背包最外层时组别的循环,因为这种题目可以划分为多个 01 背包,每个 01 背包互补相关。组别的循环就要在最外层。

第二层是是重量的枚举。这里跟 01 背包不同,因为如果这个放在最下面,会造成冲突。这里也是需要倒序的。

第三层是每个物品的枚举。

这里需要注意的是我们需要像二维 01 背包一样做一个特判,判断是否装得下,因为我们把多个 01 背包合在了一个维度。

下一步就像 01 背包一样了,不多说了~


其他背包就不多讲了,我们其实只要找到背包的:

  1. 总钱数
  2. 每个物品(多维也行)
  3. 时间限制和特殊说明(有可能有)
  4. 我会不会做这道题(doge)

简单dp

P1481 魔族密码 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

比如这道题,我们按题意定义 dp[i] 为第 i i i 个字符串的答案是多少。

还是按背包的思路,我们有两种选择,就是选不选这个字符串。

如果不选,那么直接不动。

如果选的话,遍历第 1 1 1 个字符串到第 i − 1 i - 1 i1 个字符串,看看这个字符串是接着的哪个字符串。找到后就从那个字符串 + 1

对于这道题,我们采取的思路是选或不选,这个思路也可以推广到很多 dp 的题,包括 2022 年,csp-j 的第四题。

还有一个窍门就是我们可以直接按题目的问题去 ”抄“状态。

比如这道题,问题是

现在你要做的就是在一个给定的单词表中取出一些词,组成最长的词链,就是包含单词数最多的词链。将它的单词数统计出来

那么我们就设 dp[i] 为第 i i i 个字符串的答案是多少。

又比如说P1359 租用游艇 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

问题是

计算出从游艇出租站 1 到游艇出租站 n 所需的最少租金。

那么直接设 dp[i] = 从第一站到第 i 站所需的最少租金

#include <bits/stdc++.h>

using namespace std;
/*
*/
int a[1005][1005], dp[100005], n;
int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i ++) {
        for (int j = i + 1; j <= n; j ++) {
            cin >> a[i][j];
        }
    }
    for (int i = 2; i <= n; i ++) {
        for (int j = 1; j <= i; j ++) {
            dp[i] = (!dp[j] ? dp[j] + a[j][i] : min(dp[i], dp[j] + a[j][i]));
        }
    }
    cout << dp[n];
    return 0;
}

一定要记住

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一篇关于区间DP学习笔记,希望对你有所帮助。 ### 什么是区间DP 区间 DP 是一种动态规划算法,用于解决一些区间上的问题。具体来说,区间 DP 通常用于解决如下问题: - 最长公共子序列(LCS) - 最长递增子序列(LIS) - 最大子段和 - 区间选数问题 区间 DP 通常采用分治或递推的方式进行求解,具体方法取决于问题的性质。 ### 区间 DP 的递推方法 区间 DP 的递推方法通常有两种,一种是自底向上的递推方法,一种是自顶向下的记忆化搜索方法。 自底向上的递推方法通常采用二维数组或三维数组来记录状态转移方程,具体的递推方式如下: ```cpp for (int len = 2; len <= n; len++) { for (int i = 1; i <= n - len + 1; i++) { int j = i + len - 1; for (int k = i; k < j; k++) { // 状态转移方程 } } } ``` 其中,len 表示区间长度,i 和 j 分别表示区间的左右端点,k 表示区间的划分点。 自顶向下的记忆化搜索方法通常采用记忆化数组来记录状态转移方程,具体的递推方式如下: ```cpp int dp(int i, int j) { if (i == j) return 0; if (memo[i][j] != -1) return memo[i][j]; memo[i][j] = INF; for (int k = i; k < j; k++) { memo[i][j] = min(memo[i][j], dp(i, k) + dp(k + 1, j) + ...); } return memo[i][j]; } ``` 其中,i 和 j 分别表示区间的左右端点,k 表示区间的划分点,memo 数组用于记忆化状态转移方程。 ### 区间 DP 的优化 对于一些区间 DP 问题,我们可以通过一些技巧和优化来减少时间和空间的消耗。 一种常见的优化方式是状态压缩,将二维或三维数组压缩成一维数组,从而减少空间的消耗。 另一种常见的优化方式是使用滚动数组,将数组的维度从二维或三维减少到一维,从而减少时间和空间的消耗。 此外,对于一些具有特殊性质的区间 DP 问题,我们还可以使用单调队列或单调栈等数据结构来进行优化,从而减少时间和空间的消耗。 ### 总结 区间 DP 是一种常用的动态规划算法,用于解决一些区间上的问题。区间 DP 通常采用分治或递推的方式进行求解,具体方法取决于问题的性质。对于一些区间 DP 问题,我们可以通过一些技巧和优化来减少时间和空间的消耗。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值