多重背包二进制分解思想

多重背包转换成 01 背包问题就是多了个初始化,把它的件数C 用
分解成若干个件数的集合,这里面数字可以组合成任意小于等于C
的件数,而且不会重复,之所以叫二进制分解,是因为这样分解可
以用数字的二进制形式来解释
比如:7的二进制 7 = 111 它可以分解成 001 010 100 这三个数可以
组合成任意小于等于7 的数,而且每种组合都会得到不同的数
15 = 1111 可分解成 0001  0010  0100  1000 四个数字
如果13 = 1101 则分解为 0001 0010 0100 0110 前三个数字可以组合成
7以内任意一个数,加上 0110 = 6 可以组合成任意一个大于6 小于13
的数,虽然有重复但总是能把 13 以内所有的数都考虑到了,基于这种
思想去把多件物品转换为,多种一件物品,就可用01 背包求解了。

看代码:
int n;  //输入有多少种物品
int c;  //每种物品有多少件
int v;  //每种物品的价值
int s;  //每种物品的尺寸
int count = 0; //分解后可得到多少种物品
int value[MAX]; //用来保存分解后的物品价值
int size[MAX];  //用来保存分解后物品体积
scanf("%d", &n);    //先输入有多少种物品,接下来对每种物品进行分解
while (n--) {   //接下来输入n中这个物品
        scanf("%d%d%d", &c, &s, &v);  //输入每种物品的数目和价值
        for (int k=1; k<=c; k<<=1) { //<<右移 相当于乘二
            value[count] = k*v;
            size[count++] = k*s;
            c -= k;
        }
        if (c > 0) {
            value[count] = c*v;
            size[count++] = c*s;
        }
    }
现在用count 代替 n 就和01 背包问题完全一样了


Hdu 2191
#include
#include
#define N 150
int max(int a,int b)
{
    return a>b?a:b;
}
int main()
{
    int t,n,m,i,j;
    int v[N],w[N],c[N],dp[N],count,value[N*10],size[N*10];
    //v[]存价值,w[]存尺寸,c[]存件数,在本题中,价值是米的重量,尺寸是米的价格
    //count存储分解完后的物品总数  
 //value存储分解完后每件物品的价值  
//size存储分解完后每件物品的尺寸
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        count=0;
        for(i=0;i         {
            scanf("%d%d%d",&w[i],&v[i],&c[i]);
            //对该种类的c[i]件物品进行二进制分解
            for(j=1;j<=c[i];j<<=1)
            {
                value[count]=j*v[i];
                size[count++]=j*w[i];
                c[i]-=j;
            }
            if(c[i]>0)
            {
                value[count]=c[i]*v[i];
                size[count++]=c[i]*w[i];
            }
        }
            //经过上面对每一种物品的分解,  
        //现在Value[]存的就是分解后的物品价值  
        //size[]存的就是分解后的物品尺寸  
        //count就相当于原来的n  
        //下面就直接用01背包算法来解
        memset(dp,0,sizeof(dp));
        for(i=0;i             for(j=n;j>=size[i];j--)
                dp[j]=max(dp[j],dp[j-size[i]]+value[i]);
        printf("%d\n",dp[n]);
    }
    return 0;
}

另外一种霸气的写法:
由前面的基础的0-1背包知识我们可以知道在做滚动数组时要逆序,而完全背包要顺序(介绍).其原因就是因为完全背包中每一种物品都是有无穷多个的,不能穷举.但是现在在多重背包中,由于每种物品的个数都有一定的限制,我们就可以在数量级允许的情况下,将多重背包直接拆分成0-1背包—-这是将复杂转换为简单问题的最直接的一种思想.在这个题目中就能这样处理.所以从这种意义上来说,多重背包其实就是0-1背包.
但是,有许多题目由于多重背包的每种物品的数量较多,此时如果全部拆分成0-1背包来做就会TLE.这时,我们需要引进的就是二进制的思想.下面简单地介绍一下二进制思想在这里的应用.
我们知道,任何一个十进制数都能转换成相应的且唯一的二进制数.而且二进制数的位数能反应出其能表示的最大十进制数.对于x位的二进制数,我们能表示最大的十进制数就是2^x-1(即x个1).
下面我们来看下多重背包的拆分,对于物品i有Bag[i]个.最简单也是效率最低的拆分方法就是拆分成Bag[i]个物品i.但是我们可以用上面提到的二进制的思想来提高拆分的效率:我们只需要拆分成二进制位数个物品即可.这样说还是过于抽象,下面举个例子来看:
对于十进制数10,我们用二进制来表示就是1010.我们只需要4个数就能表示出10个状态,这四个数分别是1,2,4,3.(之所以最后一个数是3而不是8是因为若是8会导致最大价值为1+2+4+8=15,而不是10)
1 = 1
2 = 2
3 = 1 +2
4=4
5=1+4
6=2+4
7=1+2+4
8=1+4+3
9=2+4+3
10=1+2+4+3
Hdu2191 代码:
#include
#include
struct node
{
    int x,y,z;
}a[150];
int max(int a,int b)
{
    return a>b?a:b;
}
int main()
{
    int n,m,t,i,j,q,p;
    int b[150];
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        memset(b,0,sizeof(b));
        for(i=1;i<=m;i++)
            scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
        for(i=1;i<=m;i++)
        {
            q=a[i].x*a[i].z;
            if(q>=n)
            {//完全背包
                for(j=a[i].x;j<=n;j++)
                    b[j]=max(b[j],b[j-a[i].x]+a[i].y);
            }
            else
            {
                p=1;
                q=a[i].z;
                while(p                 {//01背包
                    for(j=n;j>=p*a[i].x;j--)
                        b[j]=max(b[j],b[j-p*a[i].x]+p*a[i].y);
                    q-=p;
                    p=p*2;
                }
                for(j=n;j>=q*a[i].x;j--)
                    b[j]=max(b[j],b[j-q*a[i].x]+q*a[i].y);
            }
        }
        printf("%d\n",b[n]);
    }
    return 0;
}

多重背包二进制优化
转自:http://blog.csdn.net/ronhou/article/details/7738014
取模优化

当输入样本特别大时,比如给出上百万件物品,这时候仅靠优化算法仍然不能使运行时间降到满意的范围。可考虑如何减少输入样本。poj1014的discussion上有一个非常巧妙的“取模优化”法。
设价值为v(1<=v<=6)的物品共有n件,我们希望找到一个比较小的数s(s
?如果该问题不可分,那么n-2件v仍然不可分,依次类推,用s或 s-1替换n仍然不可分
?如果该问题可分,即可分成价值相等的两堆。分两种情况考虑:
?如果两堆里都有v。 两堆各减一个v,即n改为n-2,仍然可分,可以反复减2直至只有一堆有v。
?如果仅有一堆有v。 如果将n改为n-2仍可分,则必须满足两个条件: I.没有v的那一堆中,至少有一种其它物品可替换v。II.替换后两堆都至少有一个v。如果n>s时始终满足这两个条件,我们就可以用s或s-1替换n.
下面依次考虑v=1,2,3,4,5,6时如何根据“抽屉原理”得到满足条件I和II的s。

v=1时,s=6 替换法: if(n>6) n=6-n%2

1总能被其它价值替换,所以满足条件I不是问题,为满足条件2,s必须大于6。 因为6是其它价值物品中一次可替换最多1的物品。

v=2时,s=5 替换法: if(n>5) n=4+n%2

由1*(2-1)+3*(2-1)+4*(1-1)+5*(2-1)+6*(1-1) = 9 < 2*5知,s=4时满足条件I。但这里要注意,如果另一堆可替换2的是两个5,那么一次就可替换5个2。为满足条件 II,s不能小于5。所以这里s是5而不是4。

v=3时,s=8 替换法: if(n>8) n=8-n%2

由1*(3-1)+2*(3-1)+4*(3-1)+5*(3-1)+6*(1-1) = 24 < 3*9知,s=8时满足条件I,且最多可替换5个3,所以s=8>5也满足条件II。

v=4时,s=8 替换法: if(n>8) n=8-n%2

由1*(4-1)+2*(2-1)+3*(4-1)+5*(4-1)+6*(2-1) = 35 < 4*9知, s=8时满足条件I,且最多可替换5个4,所以s=8>5也满足条件II。

v=5时,s=12 替换法: if(n>12) n=12-n%2

由1*(5-1)+2*(5-1)+3*(5-1)+4*(5-1)+6*(5-1) = 64 < 5*13知,s=12满足条件I,且最多可替换6个5,所以s=12>6也满足条件II。

v=6时,s=7 替换法:if(n>7) n=6+n%2

由1*(6-1)+2*(3-1)+3*(2-1)+4*(3-1)+5*(6-1) = 45 < 6*8知,s=7满足条件I,且最多可替换5个6,所以s=7>5也满足条件II。

可以看出,“模优化”将无论多么大的输入样本减少到50个以内,极大地减少了计算量,从而显著提高运行效率。而“模优化”的关键就是“抽屉原理”。

你可能感兴趣的:(算法)