动态规划———背包总结详解

01背包问题

1.题目: 有N件物品和一个容量大小为V的背包,放入第i件物品的耗费是Vi,得到的价值是Wi,求解装入那些物品使得得到的价值最大。
2.特点:每个物品只有一件只需要考虑这件物品有要不要放即可。
3.dp数组及含义: 我们可以很容易想到的东西就是用一个二维数组去保存一下,
dp[i][j] : 意义就是前 i 件物品恰好放入一个容量为 j 的背包可以获得的最大价值(这里的恰好放入不一定是全部放入,容量不足以全部放入时我们为了获得更多的价值必须选择一些物品去放入)
有了这个dp数组我们现在要做的事情就是如何去转移状态,要想转移状态就要知道现在这个状态可以通过什么状态转移得到。刚刚我们就讲过了对于一个物品只有放与不放两种情况,所以我们就可以很容易得到一个转移的方程.

保证j>=v[i]
dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);

为什么是这样子的呢?
我们来看一下的到dp[i][j]的两个状态的含义:
dp[i-1][j]: 表示的就是前 i-1 个物品恰好放入一个容量为j的物品时得到的最大价值。所以,仔细想一下这不就是在前 i 件物品放入背包时不选择第 i 件物品的情况嘛!
dp[i-1][j-v[i]]+w[i]: 根据含义这不就是表示的是在背包内部腾出一个空间v[i]然后把第 i 个物品放进去的价值嘛!

联系上文,我们可以得知对于每个物品只有放与不放两种状态,而恰好上面的两个二维数组就是表示的这两种状态,所以动态转移方程迎刃而解。

4.核心代码:

        for(int i=0;i<n;i++){
            for(int j=v[i];j<=V;j++){
                dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
            }
        }

5.优化空间复杂度:

以上方法的时间和空间复杂度均为O(VN),其中时间按复杂度应该不可以再优化了,但是空间复杂度可以优化到 O(N);

可以直接用一个一维数组来做更新,因为我们每次更新完一个状态后他就是这个状态的最优解了不会发生变化了。所以没必要对每个前 i 个都存起来 只要有他的前一个状态就可以去更新了。

核心代码:

        for(int i=0;i<n;i++){
            for(int j=V;j>=v[i];j--){
                dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
            }
        }

这里有一个值得思考的问题,为什么在这里的顺序变成了V -> v[i] 了。
因为只有这样才可以保证,在每一次在计算 dp[i] 时 dp[j-v[i]]保存的是
dp[i-1][j-v[i]]的值。

注: 01背包很重要仔细理解,后续有许多内容会转化为01背包去求解。
HDU - 2602 练习题
ac code :

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn = 1e3 +10;
int v[maxn];
int w[maxn];
int dp[maxn];
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        int n,V;
        memset(dp,0,sizeof(dp));
        scanf("%d%d",&n,&V);
        for(int i=0;i<n;i++){
            scanf("%d",&w[i]);
        }
        for(int i=0;i<n;i++){
            scanf("%d",&v[i]);
        }
        for(int i=0;i<n;i++){
            for(int j=V;j>=v[i];j--){
                dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
            }
        }
        cout<<dp[V]<<endl;
    }

    return 0;
}

完全背包问题

1.题目: 有N件物品(每件物品有无限件可取)和一个容量大小为V的背包,放入第i件物品的耗费是Vi,得到的价值是Wi,求解装入那些物品使得得到的价值最大。
2.特点: 每件物品可以取无限多件,但是必须在背包的容量范围内。
说到这里我们可以考虑一下到底每件物品可以取多少件呢,对于每件物品来讲最多其实只可以取到 V/v[i] 件。因为受到了背包容量的限制。
直接一点我们都会去直接考虑到依次对每件物品取1,2,3,4,……,V/v[i]件。

dp[i][j]=max(dp[i-1][j-kv[i]]+kw[i],dp[i][j])
k表示依次枚举每种情况

3.转01背包
讲到这里,我们来看一个新的东西。
一个数如果被拆分成许多二进制数的和的形式 (拆分的时候依次按照二进制1,2,4,8……大小拆分,直到剩余部分不足为一个二进制数),有什么新的发现呢,
例如:
9 = 1 + 2 + 4+ 2;
这里面 已经包含了 1, 2, 4,我们既然有九个那么我们看看是不是可以凑到9呢?
3 = 1 + 2;
5 = 1 + 4;
6 = 2 + 4;
7 = 1 + 2 + 4;
8 = 2 + 2 + 4;
9 = 1 + 2 + 4 + 2;
我们发现这些二进制数通过不同的组合可以得到1~9所有的情况,有了这个结论再想一下我们前面的01背包。你是不是恍然大悟呢!

**没错,就是你想的那样子,我们可以把一个完全背包的问题转化为01背包的问题去求解。按照二进制的形式我们对每个物品的最大值 V/v[i] 做一下拆分,然后跑01背包里面的所有情况都会跑得倒,其余就是你的01背包的知识啦!

HDU - 4508 来试试看吧

ac code:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
const int N = 110;
const int M = 1e5+10;
int v[N*30];
int w[N*30];
int dp[M];
int a[N];
int b[N];
int main()
{
    int n,m,t,cnt;
    while(~scanf("%d",&n)){
        memset(dp,0,sizeof(dp));
        cnt=0;
        for(int i=0;i<n;i++){
            scanf("%d%d",&a[i],&b[i]);
        }
        scanf("%d",&t);
        for(int i=0;i<n;i++){
            int tmp=t/b[i],pow=1;
            //cout<<"tmp:"<<tmp<<endl;
            while(tmp){
                if(tmp>=pow){
                    v[++cnt]=pow*b[i];
                    w[cnt]=pow*a[i];
                    tmp-=pow;
                    pow*=2;
                }
                else{
                    v[++cnt]=tmp*b[i];
                    w[cnt]=tmp*a[i];
                    tmp-=tmp;
                }
            }
        }
        for(int i=1;i<=cnt;i++){
            for(int j=t;j>=v[i];j--){
                dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
            }
        }
        printf("%d\n",dp[t]);
    }

    return 0;
}

多重背包问题

1.题目: 有N件物品(每件物品有 m[i] 可取)和一个容量大小为V的背包,放入第i件物品的耗费是Vi,得到的价值是Wi,求解装入那些物品使得得到的价值最大。
2,特点: 每个物品可以取Mi件,有了之前完全背包的内容相信我们很快可以理解多重背包。所以就是很简单的多重背包转01背包》
直接上题目:
HDU - 2191
ac code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn = 110;
const int M = 1010;
int v[M];
int w[M];
int dp[maxn];
int main()
{
    int t,n,m;
    scanf("%d",&t);
    while (t--){
        memset(dp,0,sizeof(dp));
        scanf("%d%d",&n,&m);
        int cnt=1;
        for(int i=1;i<=m;i++)
        {
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            int tmp=c,pow=1;
            while (tmp){
                if(tmp>=pow){
                    v[cnt]=pow*a;
                    w[cnt++]=pow*b;
                    tmp-=pow;
                    pow*=2;
                }
                else{
                    v[cnt]=tmp*a;
                    w[cnt++]=tmp*b;
                    tmp-=tmp;
                }
            }
        }
        for(int i=1;i<cnt;i++){
            for(int j=n;j>=v[i];j--){
                dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
            }
        }
        printf("%d\n",dp[n]);
    }
    return 0;
}

混合背包问题

问题: 将前面的三种背包合在一起,就是在讲有的物品可以取 1 次(01背包),有的可以取无限次(完全背包),有的可以取的次数有一个上限(多重背包)。同样还是求解最大的价值。
思路: 就是对不是01背包的部分转换为01背包求解即可.**

二维费用背包

**1.题目:**对于每件物品,具有两种不同的费用,选择这件物品必须同时付出这两种费用,对于每种费用都有一个可付出的最大值(背包容量),还是要求怎么选择可以得到最大的价值。
**设第 i 件物品所需要的两种费用分别为Ci 和 Di , 两种费用可付出的最大值(两种背包的容量)分别为V 和 U。物品的价值为Wi;
2.特点: 一个物品两个费用,一个价值。
3.思路: 直接三维数组去维护即可。费用增加了一维,只需要状态也增加一维即可,(这样的话我们对三维,斯威甚至更多的费用都有了想法吧,)
在 01背包的基础上我们做一下状态转移方程.

dp[i][v][u]=max(dp[i-1][v][u],dp[i-1][v-Ci][u-Di]+w[i]);

试试水 HDU - 2159
ac code:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
const int maxn = 200;
int a[maxn];
int b[maxn];
int dp[maxn][maxn];
int main()
{
    int n,m,k,s;
    while(~scanf("%d%d%d%d",&n,&m,&k,&s)){
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=k;i++){
            scanf("%d%d",&a[i],&b[i]);
        }
        int minn=0x3f3f3f3f;
        for(int i=1;i<=k;i++){
            for(int j=b[i];j<=m;j++){
                for(int g=1;g<=s;g++){
                    dp[j][g]=max(dp[j][g],dp[j-b[i]][g-1]+a[i]);
                    if(dp[j][g]>=n){
                        minn=min(minn,j);
                    }
                }
            }
        }
        if(minn==0x3f3f3f3f){
            printf("-1\n");
        }
        else{
            printf("%d\n",m-minn);
        }
    }
}

分组背包

1.题目: 有N件物品和一个容量为V的背包,第 i 件物品的费用是v[i] , 价值是 w[i] ,这些物品被划分为K组,每组中的物品互相冲突,最多选一件,求解将哪些物品装入背包可使得物品的费用总和不超过背包容量,且总价值最大。
2.特点: 同组中的物品冲突,对于每组的物品只有意见都不选和选其中的一件。
3.dp数组及其意义: dp[i][j]表示前 i 组物品花费费用为 j 能取得的最大值。

dp[i][j]=max(dp[i-1][j],dp[i-1][v-v[i]]+w[i]);

4.使用一维数组代码:(请读者自己思考,转移过程)。
直接看题目 HDU - 1712
ac code ;

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cmath>
    #include <cstring>
    using namespace std;
    const int maxn = 110;
    int w[maxn][maxn];
    int dp[maxn];
    int main()
    {
        int n,m,cnt;
        while(scanf("%d%d",&n,&m)&& n+m){
            cnt=1;
            memset(dp,0,sizeof(dp));
            for(int i=1;i<=n;i++){
                for(int j=1;j<=m;j++){
                    scanf("%d",&w[i][j]);
                }
            }
            for(int i=1;i<=n;i++){
                for(int j=m;j>=0;j--){
                    for(int k=1;k<=j;k++){
                        dp[j]=max(dp[j],dp[j-k]+w[i][k]);
                    }
                }
            }
            printf("%d\n",dp[m]);
        }
    }

后续更难一点的下次更新。
如有错,请指正,感激不尽。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值