背包问题是动态规划的基础,也是最易理解的动态规划,有著名的背包九讲可以查询,我就稍微写一下。

  1. 01背包

  这个是最基础的背包,即这个物品只有一件,只需确定放还是不放即可。

  二维状态转移方程:f[i,x]=max{f[i-1,x],f[i-1,x-c[j]]+w[j]}

  一维状态转移方程:f[x]=max{f[x],f[x-c[j]]+w[j]}

  需要注意的是,使用一维时,枚举x需要逆序,这样才可以保证f[x]不受前后影响,二维则随意。

  同时,我们还需要注意题目,若题目要求恰好装满背包,则初始化f[0]=0,其他为负无穷;若没要求恰好装满,则全部初始化为0。

  hdu2602为模板题。

#include <cstdio>
#include <cstring>
#define FOR(i,x,y) for(int i=x;i<=y;++i)
#define rFOR(i,x,y) for(int i=x;i>=y;--i)
int f[1010],t,v,n,a[1010][2];
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        memset(f,0,sizeof(f));//数组初始化
        scanf("%d%d",&n,&v);
        FOR(i,0,n-1) scanf("%d",&a[i][0]);
        FOR(i,0,n-1) scanf("%d",&a[i][1]);
        FOR(i,0,n-1) rFOR(x,v,a[i][1]) if(f[x]<f[x-a[i][1]]+a[i][0])
            f[x]=f[x-a[i][1]]+a[i][0];//DP方程
        printf("%d\n",f[v]);
    }
    return 0;
}

 

  2. 完全背包

  与01背包的唯一区别是,每个物品可以无限取。

  二维状态转移方程:f[i,x]=max{f[i-1,x],f[i,x-c[j]]+w[j]}

  一维状态转移方程:f[x]=max{f[x],f[x-c[j]]+w[j]}

  可以发现,二维状态转移方程与01背包的有所不同,因为每个物品是可以无限获取的;而一维的状态转移方程完全相同,我们需要做的只是改一下循环的顺序,01背包中使用逆序而完全背包中使用顺序,就可以不断使用这个物品。

  另外,当数据随机时可以考虑一个简单的优化,即删去单价小的物品。

  hdu1114为模板题。

 

#include <cstdio>
#include <cstring>
#define FOR(i,x,y) for(int i=x;i<=y;++i)
#define rFOR(i,x,y) for(int i=x;i>=y;--i)
#define MIN(a,b) a>b?b:a
int t,k1,k2,n,m,f[10010],c[510],w[510];
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        memset(f,0x3f,sizeof(f));
        f[0]=0;
        scanf("%d%d",&k1,&k2);
        m=k2-k1;
        scanf("%d",&n);
        FOR(i,0,n-1) scanf("%d%d",&w[i],&c[i]);
        FOR(i,0,n-1) FOR(x,c[i],m) f[x]=MIN(f[x],f[x-c[i]]+w[i]);
        if(f[m]<0x3f3f3f3f) printf("The minimum amount of money in the piggy-bank is %d.\n",f[m]);
        else printf("This is impossible.\n");
    }
    return 0;
}

 

 

  3. 多重背包

  多重背包和01背包相似,都是有限背包问题。

  时间要求可以达到的情况下O(VNK)的复杂度就可以了,另外有一种优化方法。利用二进制的思想,把M的Weight拆分成1,2...,2^(k-1),M-2^k+1,然后利用01背包的方法即可,此时复杂度为O(VNlogK).

  hdu2191为模板题。

#include <cstdio>
#include <cstring>
#define FOR(i,x,y) for(int i=x;i<=y;++i)
#define rFOR(i,x,y) for(int i=x;i>=y;--i)
#define MAX(a,b) a>b?a:b
int t,n,m,f[110],w[110],c[110],k[110];
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        memset(f,0,sizeof(f));
        scanf("%d%d",&m,&n);
        FOR(i,0,n-1) scanf("%d%d%d",&w[i],&c[i],&k[i]);
        FOR(i,0,n-1) rFOR(x,m,w[i]) FOR(j,1,k[i]) if(x>=w[i]*j)
            f[x]=MAX(f[x],f[x-w[i]*j]+c[i]*j);
        printf("%d\n",f[m]);
    }
    return 0;
}

 

  4. 混合三种背包

  通过以上介绍可以发现,01背包就是多重背包的一种特例,因此可以归为一类。我们只需分类处理各种背包,就可以获得最终的答案。

  要注意的是,当某物品数量比背包容量还要大时,我们可以把多重背包转化为完全背包,这是一个很大的优化。

  下面给出hdu2844,即用到了二进制优化和背包转化的优化。

#include <cstdio>
#include <cstring>
#define FOR(i,x,y) for(int i=x;i<=y;++i)
#define rFOR(i,x,y) for(int i=x;i>=y;--i)
int n,m,c[110],k[110],f[100010],ans,temp,l;
int main()
{
    while(scanf("%d%d",&n,&m))
    {
        memset(f,0,sizeof(f));
        f[0]=1;
        ans=0;
        if(n==0&&m==0) break;
        FOR(i,0,n-1) scanf("%d",&c[i]);
        FOR(i,0,n-1) scanf("%d",&k[i]);
        FOR(i,0,n-1) if(k[i]*c[i]>=m)
        {
            FOR(x,c[i],m) f[x]=f[x]||f[x-c[i]];//完全背包。
        }else
        {
            temp=1; l=k[i];
            while(temp<=l)//二进制优化。
            {
                rFOR(x,m,c[i]*temp) if(x>=c[i]*temp) f[x]=f[x]||f[x-c[i]*temp];
                l-=temp;
                temp<<=1;
            }
            if(l) rFOR(x,m,c[i]*temp) if(x>=c[i]*temp) f[x]=f[x]||f[x-c[i]*l];
        }
        FOR(x,1,m) if(f[x]) ++ans;
        printf("%d\n",ans);
    }
    return 0;
}
//多重背包问题,用到了二进制优化,同时还可以把一些情况转化成完全背包来优化。

 

  5. 二维背包

  二维背包只是多了一个限制条件,做法与其他背包没有什么很大的区别。

  hdu2159为模板题。

#include <cstdio>
#include <cstring>
#define FOR(i,x,y) for(int i=x;i<=y;++i)
#define MAX(a,b) a>b?a:b
int n,m1,m2,v,f[110][110],c[110],w[110];
bool bo;
int main()
{
    while(scanf("%d%d%d%d",&v,&m1,&n,&m2)!=EOF)
    {
        memset(f,0,sizeof(f));
        bo=true;
        FOR(i,0,n-1) scanf("%d%d",&c[i],&w[i]);
        FOR(i,0,n-1) FOR(x,w[i],m1) FOR(y,1,m2)
            f[x][y]=MAX(f[x][y],f[x-w[i]][y-1]+c[i]);
        FOR(x,0,m1) if(f[x][m2]>=v)
        {
            printf("%d\n",m1-x);
            bo=false;
            break;
        }
        if(bo) printf("-1\n");
    }
    return 0;
}

 

  6. 分组背包

  分组就是一个组中只能选一件物品,剩下的就是普通的背包问题了。

  hdu1712为模板题。需要注意的是循环的顺序,最一层是分组的循环,第二层为容量的循环,第三层为物品的循环。这里我们发现和前面有所不同,但证明一下可以发现只有这样才可以保证每个组中最多只选择一件物品。

#include <cstdio>
#include <cstring>
#define FOR(i,x,y) for(int i=x;i<=y;++i)
#define rFOR(i,x,y) for(int i=x;i>=y;--i)
#define MAX(a,b) a>b?a:b
int f[110],n,m,c[110];
int main()
{
    while(scanf("%d%d",&n,&m))
    {
        if(n==0&&m==0) break;
        memset(f,0,sizeof(f));
        FOR(k,1,n)
        {
            FOR(i,1,m) scanf("%d",&c[i]);
            rFOR(x,m,0) FOR(i,1,m) if(x>=i)
                f[x]=MAX(f[x],f[x-i]+c[i]);
        }
        printf("%d\n",f[m]);
    }
    return 0;
}

 

  7. 有依赖的背包