dp之完全背包

完全背包类似题目(不过求最小值):​​杭电1114​

《背包九讲》
基本形式: 有 N 种物品和一个容量为 V 的背包,每种物品都有无限件可用。放入第 i 种 物品的费用是 C i ,价值是 W i
。求解:将哪些物品装入背包,可使这些物品的耗 费的费用总和不超过背包容量,且价值总和最大。

基本思路: 这个问题非常类似于01背包问题,所不同的是每种物品有无限件。也就
是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有 取 0 件、取 1 件、取 2 件……直至取 ⌊V /C i ⌋
件等许多种。 如果仍然按照解01背包时的思路,令 F[i,v] 表示前 i 种物品恰放入一个容 量为 v
的背包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方 程,像这样: F[i,v] = max {F[i − 1,v − kC i
] + kW i | 0 ≤ kC i ≤ v}

O(V N) 的算法 这个算法使

用一维数组,先看伪代码: F[0…V ]←0 for i ← 1 to N for v ← C i to

V F[v] ← max (F[v],F[v − C i ] + W i ) 你会发现,这个伪代码与01背包问题的伪代码只有 v
的循环次序不同而已。 为什么这个算法就可行呢?首先想想为什么01背包中要按照 v 递减的次序来 循环。让 v 递减是为了保证第 i
次循环中的状态 F[i,v] 是由状态 F[i − 1,v − C i ] 递
推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入 第 i 件物品”这件策略时,依据的是一个绝无已经选入第 i
件物品的子结果 F[i − 1,v − C i ] 。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加 选一件第 i
种物品”这种策略时,却正需要一个可能已选入第 i 种物品的子结 果 F[i,v − C i ] ,所以就可以并且必须采用 v
递增的顺序循环。这就是这个简单的 程序为何成立的道理。

值得一提的是,上面的伪代码中两层for循环的次序可以颠倒。这个结论有 可能会带来算法时间常数上的优化。
这个算法也可以由另外的思路得出。例如,将基本思路中求解 F[i,v−C i ] 的
状态转移方程显式地写出来,代入原方程中,会发现该方程可以等价地变形成 这种形式: F[i,v] = max (F[i −
1,v],F[i,v − C i ] + W i ) 将这个方程用一维数组实现,便得到了上面的伪代码。
最后抽象出处理一件完全背包类物品的过程伪代码: def CompletePack( F,C,W ) for v ← C to V F[v]
← max {F[v],f[v − C] + W}

个人理解: 与01背包相比,内层循环重量从0正向遍历到m正是为了使每一个物品都可以在无限次加入背包(在不超过背包承载重量的前提下)。

1、空间复杂度V(n*m)

#include<iostream>
#include<cstring>
using namespace std;
int n; //物品的个数
int m; //背包的最大容量
int value[1005]; //物品的价值
int weight[1005]; //物品的重量
int dp[1005][1005] //dp数组
/*
完全背包
空间复杂度为V(n*m)的算法
*/
void init() //初始化函数
{
for(int i=1;i<n;i++) //输入物品的价值
cin>>value[i];
for(int j=1;j<n;j++) //输入物品的重量
cin>>weight[j];
}
void CompletePack() //解决背包问题的函数
{
for(int i=1;i<=n;i++) //遍历每一个物品
{
for(int j=0;j<=m;j++) //从1到背包最大容量,遍历每一种组合方式
{
if(weight[i]<=j) //如何物品重量小于当前背包承载重量
{
if(dp[i-1][j]>dp[i][j-weight[i]]+value[i]) //如果加入该物品没有前一个状态价值大
dp[i][j]=dp[i-1][j];
else dp[i][j]=dp[i-1][j]+value[i]; //否则加入该物品
}
else dp[i][j]=dp[i-1][j]; //否则等于上一个状态
}
}
}
int main() //主函数
{
while(cin>>n>>m) //输入n物品的个数,m背包的最大容量
{
init(); //初始化函数
memset(dp,0,sizeof(dp)); //初始化dp数组
CompletePack(); //解决背包问题的函数
cout<<dp[n][m]<<endl; //输出结果
}
return 0;
}

2、空间复杂度V(m)
把dp[i][w]压缩成dp[w],即每次求第i组的时候对上一组(第i-1组)数据进行替换
由于完全背包求dp[w]时需要的是解决第i组基础上再加i物品的数据,因此求第i组的时候需要顺序求解,而且当w<w[i]时dp[w]=dp[w],因此只要循环w[i]…M即可

#include<iostream>
#include<cstring>
using namespace std;
int n; //物品的个数
int m; //背包的最大容量
int value[1005]; //物品的价值
int weight[1005]; //物品的重量
int dp[1005] //dp数组
/*
完全背包
空间复杂度为V(m)的算法
*/
void init() //初始化函数
{
for(int i=1;i<n;i++) //输入物品的价值
cin>>value[i];
for(int j=1;j<n;j++) //输入物品的重量
cin>>weight[j];
}
void CompletePack() //解决背包问题的函数
{
for(int i=1;i<=n;i++) //遍历每一个物品
{
for(int j=weight[i];j<=m;j++) //从weight[i]到背包最大容量,遍历每一种组合方式
{
if(dp[j-weight[i]]+value[i]>dp[j])
dp[j]=dp[j-weight[i]]+value[i]; //加入物品
}
}
}
int main() //主函数
{
while(cin>>n>>m) //输入n物品的个数,m背包的最大容量
{
init(); //初始化函数
memset(dp,0,sizeof(dp)); //初始化dp数组
CompletePack(); //解决背包问题的函数
cout<<dp[n][m]<<endl; //输出结果
}
return 0;
}

杭电1114题AC代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<map>
using namespace std;
int m,n;
int p[505];
int w[505];
int dp[1000005];
void solve() //完全背包代码
{
for(int i=1;i<=n;i++) //遍历每一个物品
{
for(int j=w[i];j<=m;j++) //遍历每一种重量的组合方式
{
if(dp[j-w[i]]+p[i]<dp[j]) //取极小值
dp[j]=dp[j-w[i]]+p[i]; //状态转移数组取得极小值
}
}
}
int main()
{
int t,e,f;
cin>>t;
while(t--)
{
cin>>e>>f;
m=f-e; //得到猪猪存钱罐的最大容量
cin>>n;
for(int i=0;i<1000005;i++) //因为是去最小值,所以给dp赋特别大的值
dp[i]=1000000;
dp[0]=0; //当背包所装重量为0时,其价值也为0
for(int i=1;i<=n;i++)
{
cin>>p[i]>>w[i]; //输入价值和重量
}
solve();
if(dp[m]==1000000) cout<<"This is impossible."<<endl;
else cout<<"The minimum amount of money in the piggy-bank is "<<dp[m]<<"."<<endl;
}
return 0;
}