动态规划---揹包问题

1、0-1揹包问题&POJ3624

问题:有N个物品,揹包容量为C,第i个物品的重量为wi、价值为vi。选择几个物品放入揹包,要求重量和不超过C,求最大价值。
用二维数组bag[N][C]存储,bag[i][j]表示在前i个物品下,容量为j时的最大价值,当i=N,j=C时即为题解
状态转移方程:bag[i][j]=wi>j?bag[i-1][j]:max(bag[i-1][j],bag[i-1][j-wi]+vi)
注意1:要考虑边界值,较简单
注意2:可优化储存,数组定义为bag[2][C],两行反复用即可,此时没有边界值问题
POJ3624即为简单揹包问题,为避免存储越界,使用注意二
#include<stdlib.h>
#include<stdio.h>

int bag[2][12881]={0};
int main(){
    int n,c,wi,vi;
    scanf("%d %d",&n,&c);
    int k;
    int x1=0,x2=1,t;
    while(n--)
    {
        scanf("%d %d",&wi,&vi);
        for(k=1;k<=c;k++)
        {
            if(wi>k)
            {
                bag[x2][k]=bag[x1][k];
            }else
            {
                bag[x2][k]=bag[x1][k]>(bag[x1][k-wi]+vi)?bag[x1][k]:(bag[x1][k-wi]+vi);
            }
        }
        t=x1;x1=x2;x2=t;
    }
    printf("%d",bag[x1][c]);
    return 0;
}

2、完全揹包问题&POJ1384

与01揹包的不同点在于,完全揹包的物品可重复使用,而01揹包只能使用一次。只需稍加修改状态转移方程,其他类似。这里依然使用只有两行的二维数组,改成以为数组也类似。
状态转移方程:bag[i][j]=max{bag[i][j-w]+v,bag[i-1][j]}

if(j-w>=0)
{
 bag[x2][j]=bag[x2][j-w]+p;
}
bag[x2][j]=bag[x2][j]<bag[x1][j]?bag[x2][j]:bag[x1][j];

注意:状态转移方程与01揹包的区别,01揹包:取[i-1][j-w]+v和[i-1][j]最大值,而完全揹包:取[i][j-w]+v和[i-1][j]最大值(必须正向循环)
POJ1384为完全揹包问题

#include<stdlib.h>
#include<stdio.h>

int bag[2][10001]={0};
int inf=1e9;

int main()
{
    int t;
    int e,f,fe;
    int n;
    int w,p;
    int i,j;
    int x1=0,x2=1,temp;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d %d",&e,&f);
        fe=f-e;
        for(j=1;j<=fe;j++)
        {
            bag[0][j]=inf;
            bag[1][j]=inf;
        }
        scanf("%d",&n);
        for(i=0;i<n;i++)
        {
            scanf("%d %d",&p,&w);
            for(j=1;j<=fe;j++)
            {
                if(j-w>=0)
                {
                    bag[x2][j]=bag[x2][j-w]+p;
                }
                bag[x2][j]=bag[x2][j]<bag[x1][j]?bag[x2][j]:bag[x1][j];
            }
            temp=x1;x1=x2;x2=temp;
        }
        if(bag[x1][fe]<inf)
        {
            printf("The minimum amount of money in the piggy-bank is %d.\n",bag[x1][fe]);
        }else
        {
            printf("This is impossible.\n");
        }
    }
}

3、01揹包与完全揹包的完整代码

#include <stdio.h>
#include <stdlib.h>

int bag[2][100]={{0}};

int main()
{
    int n,c;
    int w,v;
    int i;
    int x1=0,x2=1,t;
    scanf("%d %d",&n,&c);
    while(n--)
    {
        scanf("%d %d",&w,&v);
        for(i=1;i<=c;i++)
        {
            if(i>=w)
            {
                //只差这一段代码
                //01揹包
                //bag[x2][i]=bag[x1][i-w]+v>bag[x1][i]?bag[x1][i-w]+v:bag[x1][i];
                //完全揹包
                //bag[x2][i]=bag[x2][i-w]+v>bag[x1][i]?bag[x2][i-w]+v:bag[x1][i];
            }else
            {
                bag[x2][i]=bag[x1][i];
            }
        }
        printf("\n");
        t=x1;x1=x2;x2=t;
    }
    printf("%d",bag[x1][c]);
    return 0;
}

4、多重揹包

多重揹包就是限制数量的完全揹包,每种物品取有限个。
设第i层数量为ci
1、类似完全揹包的方法,内层循环直接循环ci次,顺序执行,每次与上一层比较(完全揹包是同本层比较),不过这种方法时间复杂度过高。
2、类似01揹包的方法,把ci个相同的物品组合成几个不同的物品,然后当作01揹包即可。拆分的方法是,ci=1+2+4+8+…+剩余,如8=1+2+4+1,15=1+2+4+8,这样可以组合为1~ci的所有值。

hdu2191即为多重揹包

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int bag[101];
    int t;
    int m,n;
    int w,v,c;
    int i,j,k;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d %d",&m,&n);
        for(i=0;i<=m;i++)
        {
            bag[i]=0;
        }
        for(i=0;i<n;i++)
        {
            scanf("%d %d %d",&w,&v,&c);
            int sum=0;
            for(k=1;;)
            {
                for(j=m;j>=1;j--)
                {
                    if(k*w<=j)
                    {
                        bag[j]=bag[j-k*w]+v*k>bag[j]?bag[j-k*w]+v*k:bag[j];
                    }
                }
                sum+=k;
                if(sum==c)
                {
                    break;
                }else if(sum+k*2>c)
                {
                    k=c-sum;
                }else
                {
                    k*=2;
                }
            }
        }
        printf("%d\n",bag[m]);
    }
    return 0;
}

poj1742,还是转01揹包的做法,要加上剪枝,不然会超时。

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int n,m;
    int bag[100001];
    int an[100],cn[100];
    int i,j,k;
    int w;
    bag[0]=1;
    while(scanf("%d %d",&n,&m)!=EOF&&n&&m)
    {
        for(i=1;i<=m;i++)
        {
            bag[i]=0;
        }
        for(i=0;i<n;i++)
        {
            scanf("%d",&an[i]);
        }
        for(i=0;i<n;i++)
        {
            scanf("%d",&cn[i]);
        }
        for(i=0;i<n;i++)
        {
            if(an[i]*cn[i]>=m)
            {
              w=an[i];
              for(j=w;j<=m;j++)
              {
                  if(!bag[j])
                  {
                      bag[j]=bag[j-w];
                  }
              }
            }else
            {
                int sum=0;
                for(k=1;;)
                {
                    w=an[i]*k;
                    for(j=m;j>=w;j--)
                    {
                        if(!bag[j])
                        {
                            bag[j]=bag[j-w];
                        }
                    }
                    sum+=k;
                    if(sum==cn[i])
                    {
                        break;
                    }else if(sum+k*2>cn[i])
                    {
                        k=cn[i]-sum;
                    }else
                    {
                        k*=2;
                    }
                }
            }
        }
        int num=0;
        for(i=1;i<=m;i++)
        {
            if(bag[i]) num++;
        }
        printf("%d\n",num);
    }
    return 0;
}
点赞