背包问题
01背包问题(组合dp)
- 题意:
N件物品 背包总体积为V 每件物品的体积为v[i],价值为w[i] 将哪几件物品装入,可保证总体积不超过V,且总价值最大?求最大总价值。
(01背包:每件物品只能使用一次!) - 动态规划是
不断决策求最优解
的过程,「0-1 背包」即是不断对第 i 个物品的做出决策,「0-1」正好代表不选与选两种决定。 - 背包问题的最暴力解法:二维拓展优化(朴素做法)
背包问题的状态表示:
f [ i ] [ j ] f[i][j] f[i][j] : 表示前 i i i 个物品,背包容量是 j j j 的情况下的最优解(最大总价值)。
计算状态转移方程 f [ i ] [ j ] f[i][j] f[i][j]:
① 当前不选第 i i i 个物品, f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j]=f[i-1][j] f[i][j]=f[i−1][j] (考虑前 i − 1 i-1 i−1个物品,背包容积是 j j j时的最优解)。
② 若 j < v [ i ] j<v[i] j<v[i] 说明第 i i i 轮背包容量不够放第 i i i 个,只能去①。否则,当前选第 i i i 个物品, f [ i ] [ j ] = f [ i − 1 ] [ j − v [ i ] ] + w [ i ] f[i][j]=f[i-1][j-v[i]]+w[i] f[i][j]=f[i−1][j−v[i]]+w[i] (考虑前 i − 1 i-1 i−1个物品,背包容量是 j − v [ i ] j-v[i] j−v[i]时的最优解)。
f [ i ] [ j ] = m a x { ① , ② } f[i][j]=max\{①,②\} f[i][j]=max{①,②}
答案:
r e s u l t = f [ N ] [ V ] result=f[N][V] result=f[N][V]
#include <bits/stdc++.h>
using namespace std;
int f[1010][1010],v[1010],w[1010];
int main(){
int N,V;
cin >> N >> V;
for(int i=1;i<=N;i++){
cin >> v[i] >> w[i];
}
for(int i=1;i<=N;i++){
for(int j=0;j<=V;j++){
f[i][j]=f[i-1][j]; /* 最大总价值即i-1,j时的最大总价值 */
if(j-v[i]>=0)
f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]); /* 注意,当前选第i个物品时,i-1总体积为j-v[i]时的最大总价值需要加上i的价值,
然后与不选第i个物品的总价值比较,取大者,才等于i,j时的最大总价值 */
}
}
int res=0;
for(int i=0;i<=V;i++){ /* N个物品的情况下,分别看看0~V体积哪个总价值最大 */
res=max(res,f[N][i]);
}
cout << res;
return 0;
}
- 二维优化为一维:
f[j]
表示N
件物品,背包容量j
下的最优解- 为什么要从
V
逆序枚举j
呢?
假如枚举到:
i=3, j=8, v[3]=5, w[3]=1
二维:
dp[3][8] = max(dp[2][8], dp[2][3] + w[3])
。此时的dp[2][8]
和dp[2][3]
都是上一轮的状态值。
转换成一维:
dp[8] = max(dp[8], dp[3] + w[3])
我们要保证第一个dp[8]
是第i
轮的,第二个dp[8]
是第i-1
轮的,dp[3]
是i-1
轮的。
而按照逆序的顺序,一维dp数组的更新顺序为:dp[8], dp[7], dp[6], … , dp[3]。也就是说,在
i
轮更新的dp[8]
,不会影响i
轮中其他未更新的值dp[3]
!此时的dp[3]仍是i-1
轮的状态值!
而如果按照顺序,dp[3]将先于dp[8]进行更新,则
dp[8] = max(dp[8], dp[3] + w[3])
中dp[3]就变成了二维数组中的dp[i][3]而不是dp[i-1][3],不符合题意了。
- 优化结果(压缩二维,只比二维节省了空间):
#include <bits/stdc++.h>
using namespace std;
int f[1010],v[1010],w[1010];
int main(){
int N,V;
cin >> N >> V;
for(int i=1;i<=N;i++){
cin >> v[i] >> w[i];
for(int j=V;j>=v[i];j--)
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
cout << f[V];
return 0;
}
最长上升子序列(线性dp)
- 题意:
一个序列:3 1 2 1 8 5 6中,最长的单调递增子序列的长度是多少? 答:1 2 5 6。长度为4。
- 闫氏dp分析法:
- ①状态表示:
f[i]
表示以a[i]
结尾的最长的上升序列。 - 集合:所有以
a[i]
结尾的上升子序列。属性:子序列长度。 - ②状态转移:
f[i] = max(f[i], f[j] + 1)
。 - 把前
i−1
个数字中所有满足条件a[j] < a[i]
(“上升”) 的j
找出来,那么f[i]
就可以试着更新为以a[j]
结尾的最长上升子序列的长度 + 自己的长度 1,但可能更新完的结果没有之前更新过的f[i]
大,最后两者取一个max
。 - 复杂度: O ( n 2 ) O(n^2) O(n2) 即:[ 状态数 ( n ) ∗ (n)* (n)∗转移数 ( n ) (n) (n) ]
#include <bits/stdc++.h>
using namespace std;
int a[1010],f[1010];
int main(){
int n;
cin >> n;
for(int i=1;i<=n;i++){
cin >> a[i];
}
for(int i=1;i<=n;i++){
f[i]=1; //f[i]可能只有一个元素a[i],没有倒数第二个元素,因此初值设为1。
for(int j=1;j<i;j++){
if(a[j]<a[i]){ //找比a[i]小的(这样才符合“上升”)
f[i]=max(f[i],f[j]+1);
}
}
}
int res=-1;
for(int i=1;i<=n;i++){
res=max(res,f[i]);
}
cout << res;
return 0;
}