HDU2602/HDU1114/HDU2191(重新整理一下01背包,完全背包,多重背包)

版权声明:本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。转载请注明出处。 https://blog.csdn.net/riba2534/article/details/54342243

好长时间不做背包的问题,有一点遗忘,现在把这些问题整理一下~

一.01背包(HDU2602)

题目:http://acm.hdu.edu.cn/showproblem.php?pid=2602

题意就是普通的01背包,给出n种物品和背包容量,给出每种物品的重量和价值,求当前背包最多能达到的价值.(c[i]表示价值,w[i]表示重量)

在二维中,dp[i][j]表示把前i件物品放入容量为v的背包中所能达到的最大价值,状态转移方程为:dp[i][j]={max(dp[i][j],dp[i-1][j-w[i]]+c[i])}.

在这里用一维的方法来解决这个问题,dp[v]表示当前是容量为v的背包能达到的最大价值,状态转移方程为:dp[v]=max{f[v],f[v-c[i]]+w[i]}.

代码:

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <stack>
#include <queue>
#include <vector>
#include <algorithm>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
int c[2000];//价值
int w[2000];//重量
int dp[2000];
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n,v;
        mem(dp,0);
        scanf("%d%d",&n,&v);
        for(int i=0; i<n; i++)scanf("%d",&c[i]);
        for(int i=0; i<n; i++)scanf("%d",&w[i]);//读入数据
        for(int i=0; i<n; i++)//容量v在前i件物品时可以得到的最大值
            for(int j=v; j>=w[i]; j--)//注意这里是逆序,可以避免重复放置
                dp[j]=max(dp[j],dp[j-w[i]]+c[i]);
        printf("%d\n",dp[v]);
    }
    return 0;
}


二.完全背包(HDU1114)

题目:http://acm.hdu.edu.cn/showproblem.php?pid=1114

思路:题意是给出了小猪存钱罐的重量和存钱罐最多能承受的重量(算出它的承重),然后几组数据,包括钱币的价值与重量,求最多装多少钱。这是一个完全背包问题,钱币有无限件可以用,按照上面的01背包的思路,写出状态转移方程:f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k*c[i]<=v},用一维来表示:f[v]=max{f[v],f[v-c[i]]+w[i]},看上去和01背包一样,但是注意,这里的循环要顺序写,因为每种背包都是无限的。当我们把i从1到N循环时,f[v]表示容量为v在前i种背包时所得的价值,这里我们要添加的不是前一个背包,而是当前背包。所以我们要考虑的当然是当前状态。引用背包九讲中的话“你会发现,这个伪代码与P01的伪代码只有v的循环次序不同而已。 为什么这样一改就可行呢?首先想想为什么P01中要按照v=V..0的逆序来循环。这是因为要保证第i次循环中的状态f[i][v]是由状态f[i-1] [v-c[i]]递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的 子结果f[i-1][v-c[i]]。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果f[i][v-c[i]],所以就可以并且必须采用v=0..V的顺序循环。这就是这个简单的程序为何成立的道理。

代码:

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <stack>
#include <queue>
#include <vector>
#include <algorithm>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
int value[1000];
int weight[1000];
int dp[1000005];
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int v,wa,wb;
        scanf("%d%d",&wa,&wb);
        v=wb-wa;//减去本身的重量
        int n;
        scanf("%d",&n);
        for(int i=0; i<n; i++)
            scanf("%d%d",&value[i],&weight[i]);
        for(int i=0; i<=v; i++)
            dp[i]=10000000;//求最小的,所以初始化为最大值
        dp[0]=0;
        for(int i=0; i<n; i++)
            for(int j=weight[i]; j<=v; j++)
                dp[j]=min(dp[j],dp[j-weight[i]]+value[i]);
        if(dp[v]==10000000)
            printf("This is impossible.\n");
        else
            printf("The minimum amount of money in the piggy-bank is %d.\n",dp[v]);
    }
    return 0;
}
三、多重背包(HDU2191)

题目:http://acm.hdu.edu.cn/showproblem.php?pid=2191

思路:多重背包和以上两种的不同点在于,多重背包给了具体的重量,价钱,数量,可以转换为01背包求解(代码1),也可以进行二进制优化,将第i种物品分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数。使这些系数分别为 1,2,4,...,2^(k-1),n[i]-2^k+1,且k是满足n[i]-2^k+1>0的最大整数。例如,如果n[i]为13,就将这种 物品分成系数分别为1,2,4,6的四件物品,这种方法有模板(代码2),这样就将第i种物品分成了O(log n[i])种物品,将原问题转化为了复杂度为<math>O(V*Σlog n[i])的01背包问题,是很大的改进,还有楼天城的单调队列法,表示不会。。。

代码1:

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <stack>
#include <queue>
#include <vector>
#include <algorithm>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
int value[1000];//价值
int weight[1000];//重量
int num[1000];//数量
int dp[1000005];
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        mem(dp,0);
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=0; i<m; i++)
            scanf("%d%d%d",&value[i],&weight[i],&num[i]);
        for(int i=0; i<m; i++)
            for(int j=1; j<=num[i]; j++)//把数量展开
                for(int k=n; k>=value[i]; k--)
                    dp[k]=max(dp[k],dp[k-value[i]]+weight[i]);
        printf("%d\n",dp[n]);
    }
    return 0;
}
代码2:

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <stack>
#include <queue>
#include <vector>
#include <algorithm>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
int value[1000];//价值
int weight[1000];//重量
int num[1000];//数量
int dp[1000005];
int v,m;
void bag01(int c,int w)//01背包
{
    int i;
    for(i=v; i>=c; i--)
    {
        if(dp[i]<dp[i-c]+w)
        {
            dp[i]=dp[i-c]+w;
        }
    }
}
void bagall(int c,int w)//完全背包
{
    int i;
    for(i=c; i<=v; i++)
    {
        if(dp[i]<dp[i-c]+w)
        {
            dp[i]=dp[i-c]+w;
        }
    }
}
void multbag(int c,int w,int n)//多重背包
{
    if(c*n>=v)
    {
        bagall(c,w);
        return ;
    }
    int k=1;
    while(k<=n)
    {
        bag01(k*c,k*w);
        n=n-k;
        k=k*2;
    }
    bag01(n*c,n*w);
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        mem(dp,0);
        scanf("%d%d",&v,&m);
        for(int i=0; i<m; i++)
            scanf("%d%d%d",&value[i],&weight[i],&num[i]);
        for(int i=0; i<m; i++)
            multbag(value[i],weight[i],num[i]);//套模板
        printf("%d\n",dp[v]);
    }
    return 0;
}
不太会写这种总结性的,姑且看看。。。










展开阅读全文

没有更多推荐了,返回首页