文章目录
背包问题
N N N个物品,背包体积 V V V, 不一定要装满背包
01背包
:每件物品最多用一次
完全背包
:每件物品有无限个
多重背包
:每个物品有
S
i
S_i
Si个,朴素版+优化版
分组背包
:
N
N
N组,每组有若干个,水果组,蔬菜组,每组最多选择一物品,每组互斥
子集划分:不重复,不漏
动
态
规
划
=
状
态
表
示
f
(
i
,
j
)
+
状
态
计
算
动态规划=状态表示f(i,j)+状态计算
动态规划=状态表示f(i,j)+状态计算
状
态
表
示
f
(
i
,
j
)
=
状态表示f(i,j)=
状态表示f(i,j)=
集
合
(
所
有
选
法
,
条
件
(
只
考
虑
前
i
个
物
品
,
总
体
积
<
=
j
)
)
+
属
性
(
M
a
x
,
M
i
n
,
数
量
)
集合(所有选法,条件(只考虑前i个物品,总体积<=j))+属性(Max,Min,数量)
集合(所有选法,条件(只考虑前i个物品,总体积<=j))+属性(Max,Min,数量)
f ( i , j ) = m a x [ f ( i − 1 , j ) , f ( i − 1 , j − v i ) + w i ] f(i,j)=max[f(i-1,j),f(i-1,j-vi)+wi] f(i,j)=max[f(i−1,j),f(i−1,j−vi)+wi]
AcWing 2. 01背包问题
#include <iostream>
using namespace std;
const int N = 1010;
int n,m;
int v[N],w[N];
int f[N][N];
int main(){
cin >> n >> m;
for(int i = 1; i <= n; i++){
cin >> v[i] >> w[i];
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
f[i][j] = f[i-1][j];
if(j >= v[i]) f[i][j] = max(f[i][j],f[i-1][j-v[i]] + w[i]);
}
}
cout << f[n][m] << endl;
return 0;
}
f [ i ] [ j ] f[i][j] f[i][j]只用到前面一行的数据,二维可以用一维来存储
for(int i = 1; i <= n; i++){
for(int j = v[i]; j <= m; j++){ //直接从v[i]开始
f[j] = max(f[j],f[j-v[i]] + w[i]); //使用的是当前层更新的数据f[j-v[i]]]数据覆盖了
}
}
for(int i = 1; i <= n; i++)
for(int j = m; j >= v[i]; j--) //直接从v[i]开始
f[j] = max(f[j],f[j-v[i]] + w[i]); //使用的是当前层更新的数据f[j-v[i]]]数据覆盖了
AcWing 3. 完全背包问题
AcWing 3. 完全背包问题
超时写法
k == 0时包括了 f[i-1][j] 的选法,所以不需要向上面一样赋值f[i-1][j]
#include <iostream>
using namespace std;
const int N = 1010;
int v[N],w[N];
int f[N][N];
int n,m;
int main(){
ios::sync_with_stdio(false); cin.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> v[i] >> w[i];
for(int i = 1; i <= n; i++){
for(int j = 0; j <= m; j++){
for(int k = 0; k*v[i] <= j; k++){
f[i][j] = max(f[i][j],f[i-1][j-v[i]*k] + w[i]*k);
}
}
}
cout << f[n][m] << endl;
return 0;
}
f
[
i
,
j
]
=
m
a
x
(
f
[
i
−
1
,
j
]
,
f
[
i
−
1
,
j
−
v
]
+
w
,
f
[
i
−
1
,
j
−
2
v
]
+
2
w
,
.
.
.
.
f[i,j]=max(f[i-1,j],f[i-1,j-v]+w,f[i-1,j-2v]+2w,....
f[i,j]=max(f[i−1,j],f[i−1,j−v]+w,f[i−1,j−2v]+2w,....
f
[
i
,
j
−
v
]
=
m
a
x
(
−
−
−
f
[
i
−
1
,
j
−
v
]
,
−
−
f
[
i
−
1
,
j
−
2
v
]
+
w
,
f
[
i
−
1
]
[
j
−
3
v
]
+
2
w
f[i,j-v]=max( --- f[i-1,j-v],--f[i-1, j-2v]+w,f[i-1][j-3v]+2w
f[i,j−v]=max(−−−f[i−1,j−v],−−f[i−1,j−2v]+w,f[i−1][j−3v]+2w
f
[
i
,
j
]
f[i,j]
f[i,j]和同一行的
f
[
i
,
j
−
v
]
f[i,j-v]
f[i,j−v]非常相似
#include <iostream>
using namespace std;
const int N = 1010;
int v[N],w[N];
int f[N][N];
int n,m;
int main(){
ios::sync_with_stdio(false); cin.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> v[i] >> w[i];
for(int i = 1; i <= n; i++){
for(int j = 0; j <= m; j++){
f[i][j] = f[i-1][j];
if(j >= v[i]) f[i][j] = max(f[i][j],f[i][j-v[i]] + w[i]); //从同一行转移过来
}
}
cout << f[n][m] << endl;
return 0;
}
优化,二维变一维
#include <iostream>
using namespace std;
const int N = 1010;
int v[N],w[N];
int f[N];
int n,m;
int main(){
ios::sync_with_stdio(false); cin.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> v[i] >> w[i];
for(int i = 1; i <= n; i++){
for(int j = v[i]; j <= m; j++){
if(j >= v[i]) f[j] = max(f[j],f[j-v[i]] + w[i]); //这里就没有01背包,覆盖的问题了
}
}
cout << f[m] << endl;
return 0;
}
01背包代码和完全背包代码看似只有第二次遍历顺序不一样,但是含义本质差别很大
AcWing 4. 多重背包问题
暴力写法
本题目n = 100,
n
3
=
1
e
6
n^3=1e^6
n3=1e6 没超时
#include <iostream>
using namespace std;
const int N = 110;
int v[N],w[N],s[N];
int n,m;
int f[N][N];
int main(){
ios::sync_with_stdio(false); cin.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> v[i] >> w[i] >> s[i];
for(int i = 1; i <= n; i++){
for(int j = 0; j <= m; j++){
for(int k = 0; k <= s[i] && k*v[i] <= j; k++){ // k = 0包含了 f[i-1][j]的情况
f[i][j] = max(f[i][j],f[i-1][j-k*v[i]] + w[i]*k);
}
}
}
cout << f[n][m] << endl;
return 0;
}
AcWing 5. 多重背包问题 II
上一题数据量增大,必须优化!
线性dp
AcWing 898. 数字三角形
#include <iostream>
#include <cstring>
using namespace std;
const int N = 510;
int a[N][N];
int f[N][N];
int n;
int main(){
ios::sync_with_stdio(false); cin.tie(0);
memset(f,0x80,sizeof f); //有负数,就不能初始化为0了
cin >> n;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= i; j++) cin >> a[i][j];
f[1][1] = a[1][1];
for(int i = 2; i <= n; i++){ //从第二行开始,保证有正确的结果
for(int j = 1; j <= i; j++){
f[i][j] = max(f[i-1][j],f[i-1][j-1] ) + a[i][j]; //右边界不用考虑
}
}
int ans = 0x80000000;
for(int i = 1; i <= n; i++) ans = max(ans,f[n][i]);
cout << ans << endl;
return 0;
}
AcWing 895. 最长上升子序列
O
(
n
2
)
O(n^2)
O(n2)算法
#include <iostream>
using namespace std;
const int N = 1010;
int f[N];
int a[N];
int n;
int main(){
ios::sync_with_stdio(false); cin.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n; i++){
f[i] = max(f[i],1); //a[i]只有一个数
for(int j = i+1; j <= n; j++){ // 用当前的最大上升长度,更新后面的最大上升子序列
if(a[j] > a[i]) f[j] = max(f[j],f[i]+1);
}
}
int ans = 0x80000000;
for(int i = 1; i <= n; i++) ans = max(ans,f[i]);
cout << ans << endl;
return 0;
}
#include <iostream>
using namespace std;
const int N = 1010;
int f[N];
int a[N];
int n;
int main(){
ios::sync_with_stdio(false); cin.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n; i++){
f[i] = 1; //至少是1
for(int j = 1; j <= i-1; j++){ //用前面的去更新当前i的最长上升子序列
if(a[j] < a[i]) f[i] = max(f[i],f[j]+1);
}
}
int ans = 0x80000000;
for(int i = 1; i <= n; i++) ans = max(ans,f[i]);
cout << ans << endl;
return 0;
}
AcWing 896. 最长上升子序列 II
AcWing 896. 最长上升子序列 II
所有不同长度的最长上升子序列最小结尾的值存下来
i
f
q
4
<
a
i
产
生
了
长
度
为
5
的
最
长
上
升
子
序
列
,
更
新
一
下
q
5
if q_4<a_i产生了长度为5的最长上升子序列,更新一下q_5
ifq4<ai产生了长度为5的最长上升子序列,更新一下q5
这种做法更像贪心
n l o g ( n ) nlog(n) nlog(n)
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100010;
int q[N]; //不同长度的最长上升子序列最小结尾
int a[N];
int n;
int main(){
ios::sync_with_stdio(false); cin.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
memset(q,0x7f,sizeof q);
int ans = 0;
for(int i = 1; i <= n; i++){
int t = a[i];
//二分 t
int l = 1, r = i-1;
while(l <= r){ // lower_bound
int mid = l + (r-l)/2;
if(l == r && q[mid] >= t) break;
if(q[mid] < t) l = mid + 1; //q[mid] = t 时,覆盖最小值
else r = mid;
}
q[l] = min(q[l],t); //如果当前值相同,覆盖当前值,如果不同,那么计算长度+1的最长上升子序列的最小结尾
ans = max(ans,l); //找最大的长度下标
}
cout << ans << endl;
return 0;
}
简化写法
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100010;
int q[N]; //不同长度的最长上升子序列最小结尾
int a[N];
int n;
int main(){
ios::sync_with_stdio(false); cin.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
int ans = 0;
for(int i = 1; i <= n; i++){
int t = a[i];
//二分 t
int l = 1, r = ans; // r = 找到的最大长度,保证数组是有序的
while(l <= r){ // lower_bound
int mid = l + (r-l)/2;
if(l == r && q[mid] >= t) break;
if(q[mid] < t) l = mid + 1; //q[mid] = t 时,覆盖最小值
else r = mid;
}
q[l] = t; // t一定是最小的
ans = max(ans,l);
}
cout << ans << endl;
return 0;
}
AcWing 897. 最长公共子序列
AcWing 897. 最长公共子序列
集合:所有在第一个序列的前i个字母出现,且在第二个序列的前j个字母中出现的子序列
属性:长度的最大值
状态计算:以
a
i
,
b
j
a_i,b_j
ai,bj 是否包含在子序列之中,有四种情况
f
[
i
−
1
,
j
]
包
含
01
这
种
情
况
f[i-1,j]包含01这种情况
f[i−1,j]包含01这种情况
f
[
i
−
1
,
j
]
子
序
列
包
含
在
f
[
i
,
j
]
之
中
f[i-1,j]子序列包含在f[i,j]之中
f[i−1,j]子序列包含在f[i,j]之中
f
[
i
,
j
−
1
]
包
含
10
这
种
情
况
f[i,j-1]包含10这种情况
f[i,j−1]包含10这种情况
f
[
i
,
j
−
1
]
子
序
列
包
含
在
f
[
i
,
j
]
之
中
f[i,j-1]子序列包含在f[i,j]之中
f[i,j−1]子序列包含在f[i,j]之中
#include <iostream>
using namespace std;
const int N = 1010;
char a[N],b[N];
int f[N][N];
int n,m;
int main(){
ios::sync_with_stdio(false); cin.tie(0);
cin >> n >> m;
cin >> a+1 >> b+1;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
f[i][j] = max(f[i-1][j],f[i][j-1]);
if(a[i] == b[j]) f[i][j] = max(f[i][j],f[i-1][j-1]+1);
}
}
cout << f[n][m] << endl;
return 0;
}