zoukankan      html  css  js  c++  java
  • POJ1276 多重背包(01背包 完全背包)

    POJ1276

    题目

      多重背包模板题,给定背包容量(V),给定(N)种物品,每种物品的个数(n_i)、体积(v_i)和重量(w_i)已知,求背包能装下的物品的最大重量。对应本题就是,给定提款的金额cash,给定(N)种钱币,每种钱币的个数为(n_i)、面额(D_i)已知,求能兑换的钱币的最大值。本题中,体积和重量都等于面额。

    Sample Input

    735 3  4 125  6 5  3 350
    633 4  500 30  6 100  1 5  0 1
    735 0
    0 3  10 100  10 50  10 10
    

    Sample Output

    735
    630
    0
    0
    

    算法思路

      刚开始的想法是直接转化为01背包问题来解,即将第(i)种物品换成(n_i)件01背包中的物品,则得到了物品数为(sum{n_i})的01背包问题,直接求解复杂度为(mathcal{O}(Vsum{n_i})),测试表明会TLE。

      需要对以上的朴素想法进行优化。将第(i)件物品参考二进制的思想来拆分,即拆分乘若干件01背包中的物品,每件物品的系数分别为(2^0, 2^1, 2^2,cdots,2^{k-1},n_i-2^k+1)(k)是满足(n_i-2^k+1>0)的最大整数。例如,若(n_i=13),则可以拆分为系数分别为(1,2,4,6)四件物品。拆分的核心出发点就是,原先的(1cdots n_i)之间的任意整数都能用相应的系数组合而成。这样的话,复杂度就可以降低为(mathcal{O}(Vsum{lceil log{n_i} ceil}))

      另外,若(n_i imes v_i>V)时,针对物品(i)可以看作完全背包问题,可以用完全背包的复杂度为(mathcal{O}(NV))算法求解物品(i)

    代码

    直接转化为01背包求解

    Result: TLE

    #include <stdio.h>
    #include <string.h>
    #include <algorithm>
    
    int cash, N;
    int denominations[10 * 1000 + 5];
    int opt[100000 + 5];
    
    int main() {
    	while (scanf("%d", &cash) != EOF) {
    		memset(opt, 0, sizeof(opt));
    		scanf("%d", &N);
    		int n, D, ptr = 1;
    		for (int i = 1; i <= N; i++)
    		{
    			scanf("%d %d", &n, &D);
    			for (int j = 1; j <= n; j++)
    				denominations[ptr++] = D;
    		}
    		for (int i = 1; i < ptr; i++)
    			for (int j = cash; j >= denominations[i]; j--)
    				opt[j] = std::max(opt[j], opt[j - denominations[i]] + denominations[i]);
    		printf("%d
    ", opt[cash]);
    	}
    	return 0;
    }
    

    二进制转化+完全背包优化

      程序中01背包和完全背包都优化了空间复杂度,只需要定义opt为一维数组即可。二维数组opt到一维数组opt的空间优化没那么好理解,加一点注解。

      01背包状态转移方程:

    [egin{equation} opt[i][j] = max(opt[i-1][j], opt[i-1][j-v_i] + w_i) end{equation} ]

      对于ZeroOnePack函数的注解:
      若外层正处于第i次循环,内层循环中,j逆序减小,计算opt[j]的值时,opt[j]opt[j-cost]存储都是i-1次循环的值,即分别对应于(1)式中的(opt[i-1][j])(opt[i-1][j-v_i])。而如果j正序增大的话,计算opt[j]时,opt[j-cost]已经赋过值,存储是i次循环的值,与状态转移方程不符。

      完全背包状态转移方程:

    [egin{equation} opt[i][j] = max(opt[i-1][j], opt[i][j-v_i] + w_i) end{equation} ]

      对于CompletaPack函数的注解:
      若外层正处于第i次循环,内层循环中,j正序增大,计算opt[j]的值时,opt[j]存储的是i-1次循环的值,即对应(2)式中的(opt[i - 1][j])opt[j-cost]在本次循环中已经赋过值,存储的是第i次循环的值,对应于(2)式中的(opt[i][j-v_i]),与状态转移方程相符。opt[j] = std::max(opt[j], opt[j - cost] + weight)这个表达式表示,我正在考虑是否往背包中加一件物品i,而(opt[i][j-v_i])中可能已经包含了若干件物品i,现在考虑是否再增加物品i,所以这一点上可以反映完全背包每种物品有无穷多的性质,也正是CompletePack正确的原因。
      
    Result: 700kB, 32ms

    #include <stdio.h>
    #include <string.h>
    #include <algorithm>
    
    int cash, N;
    int opt[100000 + 5];
    
    void CompletaPack(int cost, int weight) {//原理见上文注解
    	for (int j = cost; j <= cash; j++)
    		opt[j] = std::max(opt[j], opt[j - cost] + weight);
    }
    
    void ZeroOnePack(int cost, int weight) {//原理见上文注解
    	for (int j = cash; j >= cost; j--)
    		opt[j] = std::max(opt[j], opt[j - cost] + weight);
    }
    
    void MultiplePack(int cost, int weight, int num) {
    	if (cost * num >= cash) {
    		CompletaPack(cost, weight);
    		return;
    	}
    	int k = 1;
    	while (k < num) {//二进制拆分物品
    		ZeroOnePack(k * cost, k * weight);
    		num -= k;
    		k *= 2;
    	}
    	ZeroOnePack(num * cost, num * weight);
    	return;
    }
    
    int main() {
    	while (scanf("%d", &cash) != EOF) {
    		memset(opt, 0, sizeof(opt));
    		scanf("%d", &N);
    		int num, denomination;
    		for (int i = 1; i <= N; i++)
    		{
    			scanf("%d %d", &num, &denomination);
    			MultiplePack(denomination, denomination, num);
    		}
    		printf("%d
    ", opt[cash]);
    	}
    	return 0;
    }
    

    参考:

    [1] 背包问题九讲 2.0

  • 相关阅读:
    角色总结
    cookie
    基础php链接SQL数据库
    html
    PHP 每天的总结(1)
    php的特性
    [转载]CS0234: 命名空间“System.Data”中不存在类型或命名空间名称“OracleClien...
    [转载]数据库镜像中证书过期的解决方案
    华师大陈默老师的育儿讲
    [转载]如何使用VMware Workstation 8将物理机转换为虚拟机?
  • 原文地址:https://www.cnblogs.com/wtyuan/p/12099646.html
Copyright © 2011-2022 走看看