动态规划笔记
动态规划知识点
背包问题
动态规划,算法课的时候其实就学过,但是在ACM中的应用远比算法课学的01背包等问题要多。
01背包问题
有 N 件物品和一个容量是 V的背包。每件物品只能使用一次。
第
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N行,每行两个整数 v**i,w**i,用空格隔开,分别表示第 i件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
简单理解题意,就是有一堆东西,他们有两个属性,一个是体积,一个是价值。
我们还有一个背包,背包只有一个属性,就是体积
要把选择一些东西装入背包中,每个东西只能选1次或者不选(这就是所谓的01)
最终目的是,在物品总体积不超过背包总体积的情况下,使得背包中的价值最大
分析dp的方法:状态表示(集合,属性) 状态计算
状态表示
集合:f[i,j],表示从前i个物品中取,总体积不超过j,总价值
属性:总价值的最大值
状态计算
状态实际上就是针对上面我们分析出来的集合进行拆分,拆分成能够计算的更小的状态子集
对于一个f[i,j]我们可以认为他由两个子集构成,选第i个物品,不选第i个物品
如果不选第i个物品,f[i,j]=f[i-1,j]
如果选第i个物品,用子集来表示就是f[i,j]=f[i-1,j-v[i]]+w[i] 意思就是去掉这个物品的最大价值,加上这个物品的价值,就是现在的价值
综上我们有
01背包一维优化
上述式子已经足以解决这个问题,但f[i,j]是一个二维矩阵,满足以下条件的状态转移方程可以优化为一维
- f[i,]仅仅依赖f[i-1,]
- f[i,j]依赖的f[i,g(j)]中的g(j)均在j的同侧
- 如果在j的右侧,就对j顺序遍历
- 如果在j的左侧,就对j逆序遍历
- 上述两条也不一定,需要具体问题具体分析,在01背包问题中是这样的
本质上是针对一个数组的重复利用,计算完f[i-1,]后,可以原地利用f[i-1,]的元素来计算下一级f[i,]的元素,那么这一维其实可以去掉
遍历顺序可以这样理解,以
我们通过一维优化把它优化成
f[j]会依赖f[j-v[i]]也就是j左侧,那么在更新第i层的时候,j左边的应该还是i-1层的数据,没有被改动,因此从后向前遍历。
代码中的一些注意点
i和j都是从1开始考虑,这是针对v和w而言,但是在对f进行状态转移的时候,要处理j=v[i],因为当j=v[i]时,右侧第二个式子的含义就是只选第i个物品的方案。
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int v[N];
int w[N];
int f[N];
int main()
{
int n,m;
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=m;j>=v[i];j--)
// {//j=0需要初始化
// f[j]=max(f[j],f[j-v[i]]+w[i]);
// }
for(int j=m;j>=1;j--)
{
//这里要考虑0是因为,j=v[i]时,f[0]=0,第二个式子代表的就是只选第i个物品
if(j>=v[i])
{
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
}
cout<<f[m];
}
完全背包问题
有 N 种物品和一个容量是 V的背包,每种物品都有无限件可用。
第 i种物品的体积是 v**i,价值是 w**i。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行两个整数 v**i,w**i,用空格隔开,分别表示第 i 种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
可以看到,完全背包与01背包的不同仅仅在于对于每一种物品,完全背包问题不限制使用的个数,我们用同样的dp分析方式来思考这个问题
状态表示
集合:f[i,j],表示从前i个物品中取,总体积不超过j,总价值
属性:总价值的最大值
状态计算
01背包问题中,我们是分为0,1两种状态,这里由于不限制数量,我们应该对选择数量k进行枚举
对于f[i,j]:
- 选0个第i类物品,为f[i-1,j]
- 选1个第i类物品,为f[i-1,j-v[i]]+w[i]
- ……
- 选k个第i类物品,为f[i-1,j-k*v[i]]+k*w[i]
因此
到此位置应该用暴力的方法可以解决,后面针对这个式子进行优化
注意k和项数的变化,这个式子就是上面式子去掉第一项f[i-1,j]
合并这两个式子,我们可以得到:
右侧的第一项就是上面式子少掉的一项,第二项就是上面的式子本身
在进一步优化成一维
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int v[N];
int w[N];
int f[N];
int main()
{
int n,m;
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++)
{
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
cout<<f[m];
}
多重背包问题
有 N 种物品和一个容量是 V的背包。
第 i种物品最多有 s**i 件,每件体积是 v**i,价值是 w**i。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N行,每行三个整数 v**i,w**i,s**i,用空格隔开,分别表示第 i种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
提示:
本题考查多重背包的二进制优化方法。
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10
多重背包问题的问题情景与之前两个又有所区别。
多重的意思就是对每种物品的个数都有限制si,因此需要进一步讨论。
状态表示
集合:f[i,j],表示从前i个物品中取,每个物品不超过si个,总体积不超过j,总价值
属性:最大值
状态计算
朴素的想法就还是针对每类物品,取0-si个进行讨论
状态转移方程就是
这中思路就和完全背包是类似的,但是复杂度是O(N*V*S)
下面一种方法可以优化成O(N*V*log(s))
基本的思想是针对枚举进行优化,考虑到任何一个整数都可以被若干个2的某次方加和得到
这样我们就可以不用枚举s,转而把对s的枚举拆解若干个2的某次方,对s的枚举也就转化为对这些2的某次方的枚举
更近一步,我们把这些2的某次方打包成一个整体,就可以看成新的物体,这些新物体只能选0或1次,从而转化为一个01背包问题。
一个例子
s=200
拆解为
1 2 4 8 16 32 64 73 最后的73是200-(前面数字之和)
在原来的方法中,对于200个物品,相当于是对s从0到200进行枚举,在这些里面选一个满足条件的最小的
从循环的角度就是 N->V->S 选定一些物品,选定一个体积,对物品个数枚举
现在其实是扩大了N,把对里面的S筛选整合成一些新的物体,然后对新的物体进行筛选
循环的角度就是 N*logS->V 把物品个数转化为新物品,但是每个物品只能选1次或0次,放到外层,去掉最内层的枚举
感觉这一块理解的仍然不是很到位,从问题和代码的角度目前是这样理解的
于是我们就只需要先把每一类的si个物品,转化为相应的logsi + 1个捆绑好的商品
一般形式是1 2 4 8 16 32 64 73
作为新的商品,然后对这些商品进行01背包即可
所以代码步骤就是
-
根据输入物品的价值,重量,个数。将其打包重组,这里需要用一个idx来指示重组数组的下标
-
重组的方法是先计算出最后一个k,算出前面k个次幂,最后填上最后一个s-2^(k+1)
-
idx作为01背包中的n,进行01背包的处理
#include<bits/stdc++.h> using namespace std; const int N=12000; int v[N],w[N]; int f[N]; int main() { int n,m; cin>>n>>m; int idx=0; for(int i=1;i<=n;i++) { int a,b,s; cin>>a>>b>>s; int k=(int)((log(s)/log(2))); // cout<<"k:"<<k<<endl; for(int j=1;j<=k;j++) { idx++; v[idx]=pow(2,j-1)*a; w[idx]=pow(2,j-1)*b; // cout<<v[idx]<<endl; // idx++; } //这里漏考虑最后一个填补的东西 //这里填补的对象应该是前面所有数字之和,用s去减 if((s-pow(2,k)+1)>0) { idx++; v[idx]=(s-pow(2,k)+1)*a; w[idx]=(s-pow(2,k)+1)*b; // idx++; } } n=idx; //这个也漏了 for(int i=1;i<=n;i++) { for(int j=m;j>=v[i];j--) { f[j]=max(f[j],f[j-v[i]]+w[i]); } } cout<<f[m]; }
分组背包问题
有 N 组物品和一个容量是 V的背包。
每组物品有若干个,同一组内的物品最多只能选一个。每件物品的体积是 vij,价值是 wij,其中 i 是组号,j是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。输出最大价值。
输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。
接下来有 N组数据:
- 每组数据第一行有一个整数 S**i,表示第 i 个物品组的物品数量;
- 每组数据接下来有 S**i 行,每行有两个整数 vij,wij,用空格隔开,分别表示第 i 个物品组的第 j个物品的体积和价值;
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤100
0<S**i≤100
0<vij,wij≤100
输入样例
3 5
2
1 2
2 4
1
3 4
1
4 5
输出样例:
8
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现