0-1背包问题,完全背包,多重背包

0-1背包问题

0-1背包指的是给出若干物品的价值和重量,每个物品只有一个,要求用一定的背包容量装物品后的最大价值。

可以先看个表格观察

dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少

那么可以有两个方向推出来dp[ i ][ j ],

  • 不放物品i:由dp[ i  - 1][ j ]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]就是dp[i - 1][j]。(其实就是当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以背包内的价值依然和前面相同。)
  • 放物品i:由dp[ i - 1][ j - weight[ i ] ]推出,dp[ i - 1][ j - weight[ i ] ] 为背包容量为j - weight[ i ]的时候不放物品i的最大价值,那么dp[i - 1][ j - weight[ i ] ] + value[ i ] (物品i的价值),就是背包放物品i得到的最大价值

由此可以得到状态转移方程:dp[ i ][ j ]=max( dp[ i -1 ] [ j ],v[ i ] + dp[ i -1 ] [ j - w[ i ] ] )

代码实现如下:

#include<stdio.h>
int w[110],v[110],dp[110][20100];//w[i]记录每件物品的重量,v[i]记录每件物品的价值
//dp[i][j]=max(dp[i-1][j],v[i]+dp[i-1][j-w[i]]);动态转移方程
int maxweight;//背包容量
int main()
{
    int n=0;
    scanf("%d%d",&maxweight,&n);//背包容量,物品数量
    for(int i=1; i<=n; i++)
        scanf("%d%d",&w[i],&v[i]);
    for(int i=1;i<=n;i++)
    {
        //讨论每个物品在容量1到n时的最大价值
        for(int j=1;j<=maxweight;j++)
        {
            if(w[i]<=j)
            {
                dp[i][j]=dp[i-1][j]>(v[i]+dp[i-1][j-w[i]])?dp[i-1][j]:(v[i]+dp[i-1][j-w[i]]);
            }
            else//背包放不下
                dp[i][j]=dp[i-1][j];
        }
    }
    printf("%d",dp[n][maxweight]);
    return 0;
}

其实你仔细想就可以发现,dp数组还能进行空间上的压缩。即可用个一维数组来表示,因为状态转移,我只要保留前一种状态就可以了。

dp一维数组,下标表示容量,值表示最大价值,dp[i]表示容量为i时背包所装的最大价值。

有一个物品时,dp数组情况如下:

有两个物品时,当你的容量为4时,你正着循环就会出错。如下图:

因为你需要dp[i-1][j-2],在这里即为dp[1][2],但是它已经被新的值覆盖掉了。为什么呢,因为j-w[i]<j,你想要的dp[i-1][j-w[i]]总是会在前面就被覆盖掉,所以我们得从后面开始循环。如下:

 重点:倒过来循环递推

           dp[j]:相当于dp[i-1][j]

           dp[j-w[i]]:相当于dp[i-1][j-w[i]]

#include<stdio.h>
int dp[20100];
int main()
{
    int maxweight=0,n=0,wi=0,vi=0;
    scanf("%d%d",&maxweight,&n);//背包容量,物品数量
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&wi,&vi);//物品重量,物品价值
        for(int j=maxweight;j>=wi;j--)
        {
            dp[j]=dp[j]>(dp[j-wi]+vi)?dp[j]:(dp[j-wi]+vi);//取最大值
        }
    }
    printf("%d",dp[maxweight]);
    return 0;
}

 言归正传,回到开头的问题。

因为kkksc03是双核,故单科最少总时间分配给两核的时间应尽量相同,将每科总时长的1/2记为背包容量,使用0-1背包思想解题,再用该科总时长-dp[总时长的1/2]即为该科的最少时间。

实现代码如下:

#include<stdio.h>
int num[5],test[21],dp[1000],ans;
int main()
{
    for(int i=1; i<=4; i++)
    {
        scanf("%d",&num[i]);
    }
    for(int i=1; i<=4; i++)
    {
        int sum=0;
        for(int j=1; j<=num[i]; j++)
        {
            scanf("%d",&test[j]);
            sum+=test[j];
        }
        for(int j=1; j<=num[i]; j++)
        {
            for(int k=sum/2; k>=test[j]; k--)
            {
                dp[k]=dp[k]>(dp[k-test[j]]+test[j])?dp[k]:(dp[k-test[j]]+test[j]);//优化后0-1背包的写法
            }
        }
        ans+=(sum-dp[sum/2]);
        for(int j=1; j<=sum/2; j++)dp[j]=0; //非常重要,记得清0
    }
    printf("%d",ans);
    return 0;
}

完全背包问题

 完全背包与0-1背包不同的点就在于每个物品有无限多个。,由此我们直接可以得到一个原始状态转移方程。dp[i][j]= max(dp[i-1][j],dp[i-1][j-k*w[i]]+k*v[i]) (1 <= k <= w/w[i]).

下面是一系列推导。

 最后我们得到的: dp[ i ][ j ]=max(dp[ i -1 ][ j ],dp[ i ][ j -w[ i ] ] +v[ i ])

区别就在这个 i ,完全背包是 dp[ i ][ j -w[ i ] ] ,而 01背包是 dp[ i -1 ][ j - w[ i ] ]

非常有意思的是这个方程与k无关,且与0-1背包的方程非常相似!

当然这里我们也能进行空间上的优化,但与0-1背包不同的是,他是正着来循环的!其他的是一样的!

现在我来敲一敲优化后的代码。

#include<stdio.h>
int dp[10000];
int main()
{
    int maxweigh,n,wi,vi;
    scanf("%d%d",&maxweigh,&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&wi,&vi);
        //正着循环
        for(int j=wi;j<=maxweigh;j++)
        {
            dp[j]=dp[j]>(dp[j-wi]+vi)?dp[j]:(dp[j-wi]+vi);
        }
    }
    printf("%d",dp[maxweigh]);
    return 0;
}

多重背包问题

什么叫多重背包呢。题目如下:

 多重背包与完全背包不同的是每个物品都有个数量,而不是无限多个。那么如何解决这类问题呢。核心办法就是将多重背包转化为0-1背包。比方说每种物品有si件,那么我们可以把这si件物品一件一件全部存起来,转化为有si件物品,每个物品有一件。

实现代码如下:

#include<stdio.h>
int dp[110],v[10010],w[10010];
int main()
{
    int maxweight=0,n=0,wi=0,vi=0,si=0,k=1;
    scanf("%d%d",&n,&maxweight);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d%d",&vi,&wi,&si);
        for(int j=1;j<=si;j++)
        {
            v[k]=vi;
            w[k]=wi;
            k++;
        }
    }
    for(int i=1;i<k;i++)
    {
        for(int j=maxweight;j>=v[i];j--)
        {
            dp[j]=dp[j]>(dp[j-v[i]]+w[i])?dp[j]:(dp[j-v[i]]+w[i]);
        }
    }
    printf("%d",dp[maxweight]);
    return 0;
}

上面的写法虽然好理解但不常用,还有另一种等价常用写法。

#include<stdio.h>
int dp[110],v[110],w[110],s[110];
int main()
{
    int maxweight=0,n=0;
    scanf("%d%d",&n,&maxweight);
    for(int i=1; i<=n; i++)
    {
        scanf("%d%d%d",&v[i],&w[i],&s[i]);

    }

    for(int i=1; i<=n; i++)
    {
        for(int k=1; k<=s[i]; k++)
        {
            for(int j=maxweight; j>=v[i]; j--)
            {
                dp[j]=dp[j]>(dp[j-v[i]]+w[i])?dp[j]:(dp[j-v[i]]+w[i]);
            }
        }

    }
    printf("%d",dp[maxweight]);
    return 0;
}

dp问题解题步骤

  1. 状态表示。像dp[i][j]
  2. 状态计算,即推导出状态转移方程
  3. 状态初始化,由初始化状态一步一步转移
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值