今天看了下多重背包,理解的还不够深入,不过因为是01背包过来的,所以接受起来很容易。 主要是运用了二进制的思想将一个数量为N很大的物品分为了logN个数量小的物品,而这logN个物品可以组成数量为0到N任意数量,所以这种策略是成立的。
多重背包问题有TOJ1034,TOJ1670.
TOJ1034 : 大意是有6种规格的石头,编号从1到6,编号为 i 的石头的价值为 i .现在给出各种石头的数量,问有没有可能得到总价值的一半。
做法: DP, 每种石头价值为v[i],价值为 i ,数量为 num[i] ,通过多重背包看能不能恰好取得总价值的一半。 一个优化是总价值为奇数直接不用 考虑,而在DP的时候设置一个标记来记录是否已经取得总价值一半,如果取得则可以返回。
做法2:这种方法的前提是POJ discuss里的一种做法,即给每个石头的数量mod 8。证明是用抽屉原理证的,很复杂,我没有看。但是这样以 来,数量 一下子大大减少,极大的提高了效率。 我说的做法2事实上是我的最初做法,即DFS,搜索,在搜索过程中注意剪枝,再加上 数量取模的优化,复杂度也不高。
TOJ1034(Dividing):
import java.util.Scanner; import java.util.*; import javax.swing.plaf.basic.BasicInternalFrameTitlePane.MaximizeAction; public class Main { public static int f[] = new int[20001*6]; // f[i]表示容量为 i 的背包最多能装的物品的价值 public static int v[] = new int[7]; public static int num[] = new int[7]; public static int total,flag,key; //total为 6 种大理石的总价值 ,flag为标记,一旦为1表示可以取得,可以返回了 public static void onezeropack(int cost,int weight) { // 01背包函数,注意循环是从total 到 cost,不要弄反 int i; for(i = total;i >= cost; i--) { f[i] = Math.max(f[i],f[i-cost]+weight); if(f[i] == key) { // 如果可以取得总价值一半,flag=1,返回 flag = 1; return ; } } } public static void finishpack(int cost,int weight) { int i; if(flag==1) return ; for(i = cost;i <= total; i++) { f[i] = Math.max(f[i], f[i-cost]+weight); if(f[i] == key) { flag = 1; return ; } } } public static void multipack(int cost,int weight,int amount) { if(cost*amount >= total) { finishpack(cost, weight); return ; } if(flag==1) return ; int k = 1; while(k < amount) { // 该过程即为将一件物品拆分为1,2,4...2^k 件物品进行01背包过程 onezeropack(k*cost, k*weight); amount -= k; k *= 2; } onezeropack(cost*amount, weight*amount); } public static void main(String[] args){ Scanner in = new Scanner(System.in); int i,j,cas = 1; while(true) { Arrays.fill(f,0); total = 0; flag = 0; for(i = 1;i <= 6; i++) { num[i] = in.nextInt(); v[i] = i; total += i*num[i]; } if(num[1]==0&&num[2]==0&&num[3]==0&&num[4]==0&&num[5]==0&&num[6]==0) break; if(total%2==1) flag = 0; else { key = total/2; for(i = 1;i <= 6; i++) multipack(i, i, num[i]); } System.out.println("Collection #"+cas+":"); if(flag==0) System.out.println("Can't be divided."); else System.out.println("Can be divided."); System.out.println(); cas++; } } }
Code for 1670(Cash Mechine):
#include <cstdio> #include <cstring> int f[100002],v[12],num[12],cost[12]; int total,n; int max(int a,int b){ return a>b?a:b;} void OneZeroPack(int cost,int value){ int i,j; for(i = total;i >= cost; i--) f[i] = max(f[i],f[i-cost]+value); } void completePack(int cost,int weight){ int i; for(i = cost;i <= total; i++) f[i] = max(f[i],f[i-cost]+weight); } void multiPack(int cost,int weight,int amount){ if(cost*amount >= total){ completePack(cost,weight); return ; } int k = 1; while(k < amount){ OneZeroPack(k*cost,k*weight); amount -= k; k*=2; } OneZeroPack(cost*amount,amount*weight); } int main(){ int i,j,k; while(scanf("%d%d",&total,&n)!= EOF){ memset(f,0,sizeof(f)); for(i = 1;i <= n; i++){ scanf("%d%d",&num[i],&cost[i]); v[i] = cost[i]; } for(i = 1;i <= n; i++) multiPack(cost[i],v[i],num[i]); printf("%d/n",f[total]); } }