完全背包
时间限制:3000 ms | 内存限制:65535 KB
难度:4
- 描述
-
直接说题意,完全背包定义有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的体积是c,价值是w。求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。本题要求是背包恰好装满背包时,求出最大价值总和是多少。如果不能恰好装满背包,输出NO- 输入
- 第一行: N 表示有多少组测试数据(N<7)。
接下来每组测试数据的第一行有两个整数M,V。 M表示物品种类的数目,V表示背包的总容量。(0<M<=2000,0<V<=50000)
接下来的M行每行有两个整数c,w分别表示每种物品的重量和价值(0<c<100000,0<w<100000)
- 输出
- 对应每组测试数据输出结果(如果能恰好装满背包,输出装满背包时背包内物品的最大价值总和。 如果不能恰好装满背包,输出NO)
- 样例输入
-
2 1 5 2 2 2 5 2 2 5 1
- 样例输出
-
NO 1
-
上传者
动态规划经典题;也有几种思路,最优的思路把01背包问题的第二重循环的顺序改一下,就得到了完全背包的最优解法;
这个算法使用一维数组,先看伪代码:(引用的背包9讲里面的内容)
for i=1..N
for v=0..V
f[v]=max{f[v],f[v-cost]+weight}
你会发现,这个伪代码与01背包的伪代码只有v的循环次序不同而已。 为什么这样一改就可行呢?首先想想为什么P01中要按照v=V..0的逆序来循环。这是因为要保证第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=0..V的顺序循环。这就是这个简单的程序为何成立的道理。
值得一提的是,上面的伪代码中两层for循环的次序可以颠倒。这个结论有可能会带来算法时间常数上的优化。
这个算法也可以以另外的思路得出。例如,将基本思路中求解f[i][v-c[i]]的状态转移方程显式地写出来,代入原方程中,会发现该方程可以等价地变形成这种形式:
f[i][v]=max{f[i-1][v],f[i][v-c[i]]+w[i]}
将这个方程用一维数组实现,便得到了上面的伪代码。
下面是实现的代码;动态规划的代码都很简单,最重要的是掌握其中的状态转移方程:
#include <cstdio> #include <cstring> #define max(a,b) a>b?a:b const int maxn=50001; int dp[maxn]; int main() { int n,m,v,i,j,c,w; scanf("%d",&n); while(n--) { memset(dp,-10000,sizeof(dp));//这里也要注意,在01背包中初始化给的是0,这里要初始化一个比较大的负数 dp[0]=0;//这里也要注意,没有这个就会wa scanf("%d%d",&m,&v); for(i=1;i<=m;i++) { scanf("%d%d",&c,&w); for(j=c;j<=v;j++) dp[j]=max(dp[j],dp[j-c]+w);//状态转移方程也和01背包一致 } if(dp[v]<0) printf("NO\n"); else printf("%d\n",dp[v]); } return 0; }
nyist oj 311 完全背包 (动态规划经典题)