背包问题(转)

每次增加一个物品放入背包,如果可以放入,设该物品重量为w1,价值v1,得到其放入之后背包的总重量W,减去这个物品的重量w1,那么W-w1这个重量的列的最大价值加上v1如果大于该位置(该行该列)上一行的价值的值,就更新此处的value值,表明这个物品放入背包,不然就继承上一行的value值。

第一种,0-1背包    :

dp:复习dp看

#include<bits/stdc++.h>
using namespace std;
int dp[1005];//滚动数组的写法,省下空间省不去时间
int weight[1005];
int value[1005];
int main()
{
    int n,m;
    cin>>m>>n;
    memset(dp,0,sizeof(dp));
    for(int i=1; i<=n; i++)
        cin>>weight[i]>>value[i];
    for(int i=1; i<=n; i++)//对每个数判断,可反
    {
        for(int j=m; j>=weight[i]; j--)//这里这个循环定死,不能反,反了就是完全背包
        {
            dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);//其实不断在判断最优解,一层一层的
        }
    }
    cout<<dp[m]<<endl;
    return 0;
}
             

优化为一维数组:01背包逆着 完全背包顺着

#include <iostream>
#include<algorithm>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<deque>
#include<queue>
#include<vector>
#include<stack>
#include<ctime>
using namespace std;
#define eps 1e-10
#define INF 0x3f3f3f3f
typedef long long LL;
int main()
{
    LL dp[1000],n,m,v[1000],c[1000];
    memset(dp,0,sizeof(0));
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>v[i];
    for(int i=1;i<=n;i++)
        cin>>c[i];
    for(int i=1;i<=n;i++)
    {
        for(int j=m;j>=c[i];j--)
        dp[j]=max(dp[j],dp[j-c[i]]+v[i]);
        for(int j=0;j<=m;j++)
            cout<<dp[j]<<" ";
        cout<<endl;
    }

 
 
}
第二种,完全背包:

           完全被包与0-1背包的区别就是完全背包里的物品数量是无限个,完全背包与0-1背包代码实现相差不大

dp数组正着更新是完全背包,倒着更新是0-1背包;

#include <iostream>
#include<algorithm>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<deque>
#include<queue>
#include<vector>
#include<stack>
#include<ctime>
using namespace std;
#define eps 1e-10
#define INF 0x3f3f3f3f
typedef long long LL;
int main()
{
    LL dp[1000],n,m,v[1000],c[1000];
    memset(dp,0,sizeof(0));
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>v[i];
    for(int i=1;i<=n;i++)
        cin>>c[i];
    for(int i=1;i<=n;i++)
    {
        for(int j=c[i];j<=m;j++)
        dp[j]=max(dp[j],dp[j-c[i]]+v[i]);
        for(int j=0;j<=m;j++)
            cout<<dp[j]<<" ";
        cout<<endl;
    }

 
 
}
 

第三种,多重背包:

多重背包问题限定了一种物品的个数,解决多重背包问题,只需要把它转化为0-1背包问题即可。比如,有2件价值为5,重量为2的同一物品,我们就可以分为物品a和物品b,a和b的价值都为5,重量都为2,但我们把它们视作不同的物品。

二进制分解思想:

例如有22个重量和价值都为1的物品,如果按照常规方法的话,需要循环22次,每次放入一个重量质量都为1的物品,而用二进制分解的话,可以将22个物品转化为5个物品,5个物品的重量和价值为1,2,4,8,7;这样致需要循环5次就可以了。

把22进行二进制拆分:

         成为1,2,4,8,7;由1,2,4,8可以组成1--15之间所有的数,而对于16--22之间的数,可以先减去剩余的7,那就是1--15之间的数可以用1,2,4,8表示了。

          多重背包就是背包里物品数量不为一但是有限,相当于介于0-1背包与完全背包之间,一般解决多重背包问题把

0-1背包与完全背包结合起来使用,而且一般0-1背包可以用二进制压缩来优化,降低时间复杂度

#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
LL dp[100000+10]; //存储最后背包最大能存多少
LL a[100000+10];//存的是物品的价值,或者是重量
LL c[100000+10];//存的是数量
LL m;
void ZeroonePack(LL weight,LL value) // 01背包
{
    for(LL i=m;i>=weight;i--)
        dp[i]=max(dp[i],dp[i-weight]+value);
}
void completePack(LL weight,LL value) //完全背包  
{
    for(LL i=weight;i<=m;i++)
        dp[i]=max(dp[i],dp[i-weight]+value);
}
void MultiplePack(LL w,LL v,LL num) //多重背包
{
    if(num*w>=m){//如果总容量比这个物品的容量要小,那么这个物品可以直到取完,相当于完全背包
        completePack(w,v);
        return ;
    }
    LL k=1; //否则就将多重背包转化为01背包
    while(k<=num){
        ZeroonePack(k*w,k*v);
        num=num-k;
        k=k*2; //这里采用二进制思想
    }
    ZeroonePack(num*w,num*v);
}
int main()
{
    LL n;
    while(scanf("%lld %lld",&n,&m)!=EOF)
    {
        if(n==0&&m==0)
            break;
        for(LL i=0;i<n;i++){
            scanf("%lld",&c[i]);//输入价值  此题没有物品的重量,可以理解为体积和价值相等
        }
        for(LL i=0;i<n;i++)
            scanf("%lld",&a[i]);
        memset(dp,0,sizeof(dp));
        for(LL i=0;i<n;i++)
            MultiplePack(c[i],c[i],a[i]);//调用多重背包,注意穿参的时候分别是重量,价值和数量
        LL ans=0;
        for(LL i=1;i<=m;i++)
            if(dp[i]==i)
                ans++;
        cout<<ans<<endl;
    }
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值