多重背包问题以及它的二进制及单调队列优化问题

多重背包问题,假设有物品a,b,c价格和质量分别为{1,2,3}和{1,3,4}现规定每种物品允许装入的数量为{7,5,3};然后给定背包容量n;求解背包能装下的最大价值为多少?

这个问题很像之前的01背包问题,只不过他是限定了我们对物品的数量要求,
先看解法1通法怎么解决,很简单就能分析出他的状态转移方程是       f(n)=max(f(n),f(n-k*w)+k*v);k为常量,w,v为重量和价值。

然后接下来的事情就非常简单我们直接上代码

public static  int solution_1(int n)
    {
//n为背包容量
        int[] v={1,2,1};//价值
        int[] w={1,2,1};//重量
        int[] s={3,3,3};//物品件数
        int[] dp=new int[n+1];//存储背包最大价值的数组




        for(int i=0;i<v.lenth;i++){
            for(int j=n;j>=0;j--)
               {
                    for(int k=1:k<=s[i]&&k*w[i]<=j;k++)
                    {
                        dp[j]=Math.max(dp[j],dp[j-k*w[i]]+k*v[i]);
                    }
                }

        }
        return dp[n];

     }

再来看一下解法二:对时间复杂度稍作优化,利用二进制对其进行优化.

这个过程比较难以理解大致的意思就是对可选物品的数量进行优化,因为物品的件数,也占用了一层时间复杂度,该算法的时间复杂度为O(m*n*p)约为O(n^3);我们通过对该他的可选物品数量进行优化就可以得到,O(mn*log(p))这样会讲时间复杂度降低好多个数量级。

具体实施的方法就是,我们发现通过二进制可以让某个数划分为几个数,然后这几个数字通过相加可以表示1到它的所有数字,具体的划分规则为,

for (int k=1;k<=s;k*=2);k为划分的数,然后我们举个例子,用10来,10通过2进制划分可以到1,2,4,8;然后我们发现如果加入8那么这几个数将能表示1到15的数,这是不行的,我们只允许表示1到10的数,所以在这里我们做一个小处理;
for (int k=1;k<=s;k*=2)
{
    s-=k;
   int [i]=k;
   i++
}
if(s>0){
    int[i+1]=s;
}

}

通过这样的方式我们就会得到所有正确的数来表示s;例如10这里就被分成1,2,4,3;这4个数刚好能表示10以内的所有数字.

最后我们来看看代码实现

public static int solution_2(int n)
    {
        List<bag> bags=new ArrayList<>();
        int[] v={1,2,1};//价值
        int[] w={1,2,1};//重量
        int[] ss={3,3,3};//物品件数
        int[] dp=new int[n+1];
        for(int i=0;i<v.length;i++)
        {
            int s=ss[i];

            for (int k=1;k<=s;k*=2)
            {
                s-=k;
                bags.add(new bag(v[i]*k,w[i]*k));
            }
            if(s>0){
                bags.add(new bag(v[i]*s,w[i]*s));
            }
        }
        for (bag bag:bags)
        {
            for(int i=n;i>=bag.w;i--)
            {
                dp[i]=Math.max(dp[i],dp[i-bag.w]+bag.v);
            }
        }


        return dp[n];
    }
//为了方便获取我们创建一个背包类
class bag{
int v;
int w;
public bag(int v,int w)
{
    this.v=v;
    this.w=w;
}
}

 

背包问题优化3,单调队列方式优化。

要了解这个首先建议了解一下单调队列问题,这里不做详细解释。

这个优化思路是将重量进行取模然后通过划分成不相连的子问题进行求解,做个简单思路分析,在正常情况下,我们的三层循环是,第一层,循环物品种类,第二层循环背包总容量,第三层循环物品可取件数。

这个思路将可取件数变为,对当前物品的重量取模,然后归类,每一类都是一个独立的子问题,之后通过不断对数组进行更新,就可以求解出最优解。

那么我们还是具体在代码里看吧

 public static int solution_3(int n){
        int[] v={1,2,1};//价值
        int[] w={1,2,1};//重量
        int[] ss={3,3,3};//物品件数

        int[] dp=new int[n+1];//表示f[i][]
        int[] g=new int[n+1];//表示f[i-1][]
        int[] q=new int[n+1];//数组构建的虚拟队列


        for(int i=0;i<v.length;i++)
        {
            //将g变成f[i-1];
            System.arraycopy(dp, 0, g, 0, n);
            for(int j=0;j<w[i];++j)
            {
                int head=0 ,tail=-1;//队首和队尾;
                for(int k=j;k<=n;k+=w[i])
                {
                    //dp[k]=g[k];
                    //最多ss[i]+1个元素,超出个数限制则移除队首元素
                    if(head<=tail&&k-ss[i]*w[i]>q[head])
                        ++head;
                    //若对内有值,则队首q[head]就是前k个元素的最大值,必定要取
                    //(k - q[hh]) / v为获取到的件数,相当于解法1里的k
                    if(head <= tail)
                        dp[k]=Math.max(dp[k],g[q[head]]+(k-q[head])/w[i]*v[i]);
                    //若队尾元素小于当前元素则队尾元素出队,
                    //若队内一个元素比当前元素小,则该元素一定不会被用到(单调队列思想)
                    //(q[tail]-j)/w[i]*v[i]队尾元素的值
                    //g[k]-(k-j)/w[i]*v[i])当前元素的值
                    while (head<=tail&&g[q[tail]]-(q[tail]-j)/w[i]*v[i]<=g[k]-(k-j)/w[i]*v[i])
                        --tail;
                    //去除了比当前小的元素,保证队列里的元素都比当前元素大,入队
                    q[++tail]=k;
                }
            }
        }

        return dp[n];
    }

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值