dp——01背包,分组背包,完全背包

(忽然发现自己很久以前的一篇blog还在草稿箱里……)

1. 01背包
有N 件物品和一个容量为V 的背包。放入第i 件物品耗费的空间 是vi,得到的价值是wi。求解将哪些物品装入背包可使价值总和最大。
根据题意,我们便可以设dp[i][j]为已经装了i件物品且最大容量为j得最大价值.
此时,我们就可以枚举i和j进行动态规划的状态转移得到结论.
那么dp[i][j]的最大价值是怎么转移的呢?
当第i件物品不选的时候,相当于前i件物品相同的体积j的最大价值
若选第i件,最大价值则是前i件物品体积为j-这个物品体积的最大价值加上最个物品价值
因此,我们可以得到01背包的状态转移方程:
dp[i][j]=max(dp[i−1][j],dp[i−1][j−vi]+wi)
dp[i][j]=max(dp[i−1][j],dp[i−1][j−vi]+wi)
但是,我们仍然可以在空间上选择优化:
dp[j]=max(dp[j],dp[j−vi]+wi)
dp[j]=max(dp[j],dp[j−vi]+wi)
每一层i的状态只和i-1有关,因此我们可以用1维的数组表示.
但是当前面的j转移之后枚举到后面的j是显然需要用先前j来枚举,无法满足动态规划的无后效性,因此我们再枚举j的时候需要倒叙枚举.
关于无后效性:
所谓无后效性原则,指的是这样一种性质:某阶段的状态一旦确定,则此后过程的演变不再受此前各状态及决策的影响。也就是说,“未来与过去无关”,当前的状态是此前历史的一个完整总结,此前的历史只能通过当前的状态去影响过程未来的演变。具体地说,如果一个问题被划分各个阶段之后,阶段k中的状态只能通过阶段k+1中的状态通过状态转移方程得来,与其他状态没有关系,特别是与未发生的状态没有关系,这就是无后效性。
对于不能划分阶段的问题,不能用动态规划来解;对于能划分阶段,但不符合最优化原理,也不能用动态规划来解;既能划分阶段,又符合最优化原理,但不具备无后效性原厕的,还是不能用动态规划来解;误用动态规划程序设计方法求解会导致错误的结果。
所以这一道例题代码:
#include<iostream>
using namespace std;
int n,V;
int v[100000];
int w[100000];
int dp[1000000];
int main()
{
    cin>>n>>V;
    for (int i=1;i<=n;i++)
        cin>>v[i]>>w[i];
    for (int i=1;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];
    return 0;

123456789101112131415161718
2. 完全背包
有N 件物品和一个容量为V 的背包,每件物品都有无限件。放入 第i 件物品耗费的空间是vi,得到的价值是wi。求解将哪些物品装入背 包可使价值总和最大?
与01背包不同的是完全背包的每种物品有无限件,可以重复放入。
根据01背包的求解思路,则有:
f[i][j]=max(f[i−1][j],f[i][j−vi]+wi)
就可以表示为不选和选过后可以再选,因此第二个方程是i而没有-1 。
再联系一下01背包,01背包为什么要倒叙枚举,而不是正序枚举,是因为正序枚举时f[j-v[i]]这一步已经是被覆盖的了,即有可能已经取过第i件物品,矛盾了01背包的特性,因此要倒序;但是却符合完全背包,所以i将01背包倒过来即可
所以代码:
#include<iostream>
using namespace std;
int n,V;
int v[100000];
int w[100000];
int dp[1000000];
int main()
{
    cin>>n>>V;
    for (int i=1;i<=n;i++)
        cin>>v[i]>>w[i];
    for (int i=1;i<=n;i++)
        for (int j=v[i];j<=V;j++)//这里和01背包正好相反
            dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
    cout<<dp[V];
    return 0;

1234567891011121314151617
3. 分组背包
有N 件物品和一个容量为V 的背包。第i 件物品的空间代价是ci, 价值是wi。这些物品被划分为K 组,每组中的物品互相冲突,最多选一 件。求解将哪些物品装入背包可使价值总和最大。
这种背包的问题同样十分简单,只要再状态的设置上略加改动即可.
设dp [i][j] 表示前i 组物品恰放入容量j的背包可以获得的最大价值。
我们便可以得到状态转移方程:
dp[i][j]=max(dp[i−1][j],dp[i−1][j−ci]+wi)(i属于第k组)
进行空间优化后
dp[j]=max(dp[j],dp[j-ci]+wi);
所以代码可以写为:
#include<iostream>
using namespace std;
int K,V;
int tot[10000];
int dp[1000000];
int v[2000][2000];
int w[2000][2000];
int main()
{
    cin>>K>>V;
    for (int i=1;i<=K;i++)
    {
        cin>>tot[i];
        for (int j=1;j<=tot[i];j++)
            cin>>v[i][j]>>w[i][j];
    }
    for (int i=1;i<=K;i++)
        for (int j=V;j>=0;j--)
            for (int k=1;k<=tot[i];k++)
                if (j-v[i][k]>=0)
                    dp[j]=max(dp[j],dp[j-v[i][k]]+w[i][k]);
    cout<<dp[V];
    return 0;

12345678910111213141516171819202122232425
我们可以思考思考一下:难道不会出现每件物品都重复的问题吗?
事实上是不会的,根据循环顺序,每件物品都有一个固定的j且j为倒叙枚举,即向前枚举的时候没有任意一个同组的物品存在前面状态中,故不会重复.
但是我们需要注意:这个循环的顺序是不能打乱的,必然就会产生重复的现象.
分组背包实战题(HDU 1712 ACboy needs your help)

ACboy has N courses this term, and he plans to spend at most M days on study.Of course,the profit he will gain from different course depending on the days he spend on it.How to arrange the M days for the N courses to maximize the profit?


Input
The input consists of multiple data sets. A data set starts with a line containing two positive integers N and M, N is the number of courses, M is the days ACboy has.
Next follow a matrix A[i][j], (1<=i<=N<=100,1<=j<=M<=100).A[i][j] indicates if ACboy spend j days on ith course he will get profit of value A[i][j].
N = 0 and M = 0 ends the input.


Output
For each data set, your program should output a line which contains the number of the max profit ACboy will gain.


Sample Input
2 2
1 2
1 3
2 2
2 1
2 1
2 3
3 2 1
3 2 1
0 0


Sample Output
3
4
6

题意:主角有n门课,m天,不同的课花不同的时间在里面会有不同的价值,问最大价值是多少?
完全背包的模板题,弄懂了原理,套上板子就可以了。
代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm> 
using namespace std;
int v[110][110];
int dp[110];
int main()

    int n,m,i,j,k;
    while(scanf("%d %d",&n,&m)&&(n||m))
    {
        memset(dp,0,sizeof(dp));
        for(i=1;i<=n;i++)
            for(j=1;j<=m;j++)
                scanf("%d",&v[i][j]);
                
        for(i=1;i<=n;i++)//分组数
            for(j=m;j>=1;j--)//容量体积从大到小,要满足无后效性,此处和01背包原理一样 
                for(k=1;k<=j;k++)//所有的k都属于组i 
                    dp[j]=max(dp[j],dp[j-k]+v[i][k]);
        printf("%d\n",dp[m]);
    }
    return 0;
}
/*
我们可以思考思考一下:难道不会出现每件物品都重复的问题吗? 
事实上是不会的,根据循环顺序,每件物品都有一个固定的j且j为倒叙枚举,
即向前枚举的时候没有任意一个同组的物品存在前面状态中,故不会重复. 
但是我们需要注意:这个循环的顺序是不能打乱的,必然就会产生重复的现象 
*/
 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值