01背包

01背包问题

引题

采药(luogu P1048)

题解:

根据数据规模,可以用DFS+记忆化(记忆化搜索);

但这不是官方题解。这道题最简单的办法是01背包。

何为01背包?一道例题来说明;

(acwing 2)01背包问题

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8

分析:

已知题意是:多种物品,每种物品只有一个.求能获得的最大总价值.

那么,问题在于怎么选才能获得的最大总价值

不考虑一切,本题可以用DFS做;
过程如下:

*0为不取,1为取;

每一件物品都只有2种状态:取或不取。
那么,dfs=ac。
呵呵o( ̄︶ ̄)o,你太天真了。

如果用DFS就能ac,这题的数据范围是吃****的么?

所以,请优化或改变策略。
记忆化搜索,不行。
DP,正解。

多种物品,每种物品只有一个.求能获得的最大总价值.

我们考虑是否选择第i件物品时,是需要考虑前i-1件物品对答案的贡献的.

分析
 
如果我们不选择第i件物品,那我们就相当于是用i-1件物品,填充了体积为v的背包所得到的最优解.而我们选择第i件物品的时候,我们要得到体积为v的背包,我们需要通过填充用i-1件物品填充得到的体积为v-c[i]的背包得到体积为v的背包.

//请保证理解了上文再往下看.

所以根据上面的分析,我们很容易设出该问题的二维状态

f[i][v] 代表用i件物品填充为体积为v的背包得到的最大价值.

从而很容易的写出状态转移方程

f[i][v]=max(f[i−1][v],f[i−1][v−c[i]]+w[i]);

显然的DP思想,但通常情况下,这类“每种物品都有一个价值w和体积c.,你现在有一个背包容积为V,你想用一些物品装背包使得物品总价值最大.”的问题我们称之为背包问题。

背包问题是DP中的一种重要问题。

上述问题属于背包问题中最简单的一种——01背包。

所谓01背包,就是:对于每一件物品都只有2种状态:取或不取。且每件物品有且只有一件(等价于只能取一次)。

注:各类复杂的背包问题总可以变换为简单的0-1背包问题进行求解。
(此法太过高深,本蒟蒻不会)

详细分析:

状态转移方程是如何得来的?

对于当前第 i 件物品,我们需要考虑其是否能让我们得到更优解.
显然,根据上面的话我们选择第i件物品的时候,我们要得到体积为v的背包,我们需要通过填充用i-1件物品填充得到的体积为v-c[i]的背包得到体积为v的背包.

我们需要考虑到 v−c[i] 的情况.
当不选当前第 i 件物品的时候,就对应了状态转移方程中的 f[i−1][v] ,
而选择的时候就对应了 f[i−1][v−c[i]]+w[i] .

Q:是不是在状态转移方程中一定会选择当前i物品?

A:不会

我们考虑一个问题.

如果一个体积为5的物品价值为10,而还有一个体积为3的物品价值为12,一个体积为2的物品价值为8.显然我们会选择后者.
这样我们的状态转移方程中就不一定会选择i物品。

其实最好地去理解背包问题的话,还是自己手动模拟一下这个过程,会加深理解。
代码写法↓

for(int i=1;i<=n;i++)//枚举 物品 
    for(int j=1;j<=V;j++)//枚举体积 
    //这个位置是可以正序枚举的. 
    //一维01背包必须倒序 
        if(j>=c[i])
            f[i][j]=max(f[i-1][j],f[i-1][j-c[i]]+w[i]);//状态转移方程.
        else f[i][j]=f[i-1][j].

        //上面的if语句是判断当前容量的背包能否被较小体积的背包填充得到.
        //显然 如果j-c[i]<0我们无法填充
        //(谁家背包的体积负啊 (#`O′)

但是二维下的01背包我们还是无法满足,怎么办?(空间再限制一下)

考虑一维如何写!

仔细观察会发现,二维状态中,我们的状态每次都会传递给i(就是说我们的前几行会变得没用.)

这就给了我们写一维dp的机会啊

所以我们理所当然地设状态 f[i] 代表体积为i的时候所能得到的最大价值.

则,一维的状态转移方程是

f[j]=max(f[j],f[j−c[i]]+w[i]).

容易发现的是,我们的 f[i] 只会被i以前的状态影响.
如果我们顺序枚举,我们的 f[i] 可能被前面的状态影响.
所以我们考虑倒叙枚举,这样我们的 f[i] 不会被i以前的状态影响,而我们更新的话也不会影响其他位置的状态.

(可以手绘一下这个过程,应该不是很难理解.)

代码写法↓

for(int i=1;i<=n;i++)//枚举物品 
    for(int j=V;j>=c[i];j--)//枚举体积 
        f[j]=max(f[j],f[j-c[i]]+w[i]);//状态转移方程. 

//应该不是很难理解.

小结

01背包问题是背包问题中最基础,也是最典型的问题.其状态转移方程也是基础,再次声明:各类复杂的背包问题总可以变换为简单的0-1背包问题进行求解。
请保证看懂之后再看其它问题.

补充:采药这题就是裸的01背包问题,套模板就行。
还有这不是一篇题解!!!

posted @ 2019-09-27 22:01  最寻常应是  阅读(169)  评论(1编辑  收藏  举报