ACM总结3--动态规划(dp)和背包问题

思想

如果各个子问题不是独立的,不同的子问题的个数只是多项式量级,如果我们能够保存已经解决的子问题的答案,而在需要的时候再找出已求得的答案,这样就可以避免大量的重复计算。
由此而来的基本思路是——用一个表记录所有已解决的子问题的答案,不管该问题以后是否被用到,只要它被计算过,就将其结果填入表中。

例题

1、数踏问题。有形如下图所示的数塔,从顶部出发,在每一结点可以选择向左走或是向右走,一直走到底层,要求找出一条路径,使路径上的值最大。ACM总结3--动态规划(dp)和背包问题_第1张图片
分析:这道题如果用枚举法(暴力思想),在数塔层数稍大的情况下(如31),则需要列举出的路径条数将是一个非常庞大的数目(2^30= 1024^3 > 10^9=10亿)。
从顶点出发时到底向左走还是向右走应取决于是从左走能取到最大值还是从右走能取到最大值,只要左右两道路径上的最大值求出来了才能作出决策。
同样,下一层的走向又要取决于再下一层上的最大值是否已经求出才能决策。这样一层一层推下去,直到倒数第二层时就非常明了。
如数字2,只要选择它下面较大值的结点19前进就可以了。所以实际求解时,可从底层开始,层层递进,最后得到最大值。
结论:自顶向下的分析,自底向上的计算。

2、最长递增有序子序列。
以a[]={1,4,7,2,5,8,3,6,9},计算最长递增有序子序列为例

思路:f[]用于记录a[]数组中,以对应位置数据为结尾的最长有序序列长度
p[]用于记录a[]数组中,以对应位置数据为结尾的前一个数据位置
初始化f[]={1, 1, 1, 1, 1, 1, 1,1,1},p[]={0,1,2,3,4,5,6,7,8}
计算f[i]时,f[i]=max(f[j]+1) ,(其中,a[i]>a[j],i>j>=0),意思是以a[i]为结尾,找出在a[i]前比a[i]小的数据中以该数据为结尾的最大有序子序列长度max(f[j]),则以a[i]结尾的最大有序子序列长度为max(f[j])+1。计算同时定义p[i]=j,标志a[i]为结尾的最长子序列的前一个数据a[j]的位置。同时判断此时最大长度a[i]是否比当前最大长度max大,如果a[i]更大则更新position

背包问题

模型

背包的基本模型:
给你一个容量为V的背包和若干种物品,在一定的限制条件下(每种物品都占用一定容量),问最多能放进多少价值的物品?

要求

1、最典型、最基本的DP问题;
2、理解并熟练掌握背包问题意义重大;
3、DP问题中“状态”概念的理解;
4、背包的每个容量就是“状态”,选择每个物品就是“状态的决策”;

分类

01背包
完全背包
多重背包
混合三种背包
二维费用背包
分组背包
有依赖的背包

01背包

01背包(最基础的背包问题):
有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。
问题特点:每种物品仅有一件,可以选择放或不放,用子问题定义状态:即f[i][v]表示前i件物品放入一个容量为v的背包可以获得的最大价值。
状态转移方程:
f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}

例题:
Problem Description:Many years ago , in Teddy’s hometown there was a man who was called “Bone Collector”. This man like to collect varies of bones , such as dog’s , cow’s , also he went to the grave …
The bone collector had a big bag with a volume of V ,and along his trip of collecting there are a lot of bones , obviously , different bone has different value and different volume, now given the each bone’s value along his trip , can you calculate out the maximum of the total value the bone collector can get ?
Input
The first line contain a integer T , the number of cases.
Followed by T cases , each case three lines , the first line contain two integer N , V, (N <= 1000 , V <= 1000 )representing the number of bones and the volume of his bag. And the second line contain N integers representing the value of each bone. The third line contain N integers representing the volume of each bone.
Output
One integer per line representing the maximum of the total value (this number will be less than 231).
Sample Input
1
5 10
1 2 3 4 5
5 4 3 2 1
Sample Output
14

思路:ACM总结3--动态规划(dp)和背包问题_第2张图片
问题分解:当前最优解,要么包含第i种物品,要么不包含第i种物品
DP[i][j]表示前i个物品,背包容量为j的最优值。
状态转移方程为:
DP[i][j] = max(DP[i-1][j],DP[i-1][j-v[i]] + w[i])

代码如下:

01背包问题伪代码如下:

for i = 1 to n  //所有物品
   for j = V to v[i]
        dp[j] = max(dp[j] , dp[j-v[i]] + w[i]); //倒叙遍历,如果顺序遍历,一种物品会被"取"好多次
空间成功优化到一维V

#include 
#include 
using namespace std;
int dp[1001], w[1000], c[1000];
inline int max(int a, int b) { return a>b?a:b;  }
int main()
{    int num;
    scanf("%d", &num);
    while (num--) {
        int n, V, i, j;
        scanf("%d %d", &n, &V);
        for (i=0; i<n; i++)   scanf("%d", &w[i]);
        for (i=0; i<n; i++)   scanf("%d", &c[i]);
        memset(dp, 0, sizeof(dp));
        for (i=0; i<n; i++)
            for (j=V; j>=c[i]; j--)
                dp[j] = max(dp[j], dp[j-c[i]] + w[i]);
        printf("%d\n", dp[V]);
    }
}

我们看到的求最优解的背包问题题目中,事实上有两种不太相同的问法。有的题目要求“恰好装满背包”时的最优解,有的题目则并没有要求必须把背包装满。一种区别这两种问法的实现方法是在初始化的时候有所不同。
如果是第一种问法,要求恰好装满背包,那么在初始化时除了f[0]为0其它f[1…V]均设为-∞,这样就可以保证最终得到的f[N]是一种恰好装满背包的最优解。
如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将f[0…V]全部设为0。
为什么呢?可以这样理解:初始化的f数组事实上就是在没有任何物品可以放入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量为0的背包可能被价值为0的nothing“恰好装满”,其它容量的背包均没有合法的解,属于未定义的状态,它们的值就都应该是-∞了。如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的价值为0,所以初始时状态的值也就全部为0了。

完全背包

完全背包特点:一种物品可以取无数个
可以转化成01背包问题,朴素的转化方式是和01背包类似,不过就是正着写!

多重背包

多重背包特点:
一种物品有C个(既不是固定的1个,也不是无数个)
优化的方法:
运用神奇的二进制,进行物品拆分,转化成01背包
物品拆分,把13个相同的物品分成4组(1,2,4,6)
用这4组可以组成任意一个1~13之间的数!
原理:一个数总可以用2^k表示
而且总和等于13,所以不会组成超过13的数
所以可将一种有C个的物品拆分成1,2,4,…,2(k-1),C-(2k-1)
然后进行01背包

部分代码如下:

int t = 1;
while (x>=t) {
	v[cnt] = a*t;
	c[cnt++] = b*t;
	x -= t;
	t <<= 1;
}
if (x) {
	v[cnt] = a*x;
	c[cnt++] = b*x;
}

混合三种背包

混合背包特点:
如果将三种背包问题混合起来,也就是说,有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包),应该怎么求解呢?

for i=1…N
if 第i件物品属于01背包
ZeroOnePack(c[i],w[i])
else if 第i件物品属于完全背包
CompletePack(c[i],w[i])
else if 第i件物品属于多重背包
MultiplePack(c[i],w[i],n[i])

详见:背包问题九讲

二维背包费用

二维费用背包问题:
对于每件物品,具有两种不同的费用;选择这件物品必须同时付出这两种代价;对于每种代价都有一个可付出的最大值(比如,背包容量、最大承重),求怎样选择物品可以得到最大的价值。
设第i件物品所需的两种代价分别为a[i]和 b[i],两种代价可付出的最大值(比如体积和重量)分别为V和U,物品的价值为w[i]。

对应算法:费用加了一维,只需状态也加一维即可!
设f[i][v][u]表示前i件物品付出两种代价分别为v和u时可获得的最大价值,状态转移方程则为:
f[i][v][u]=max{f[i-1][v][u],f[i-1][v-a[i]][u-b[i]]+w[i]}
详见:背包问题九讲

分组背包

N件物品和一个容量为V的背包,第i件物品的费用是c[i],价值是w[i]。这些物品被分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使物品的费用总和不超过背包容量,且价值总和最大。
对应算法:问题变成了每组物品有若干种策略:是选择本组的某一件,还是一件都不选。也就是说设f[k][v]表示前k组物品花费费用v能取得的最大权值,则有:
f[k][v]=max{f[k-1][v],f[k-1][v-c[i]]+w[i]|物品i属于组k}

伪代码如下

for 所有的组k
    for v=V..0
        for 所有的i属于组k
            f[v]=max{f[v],f[v-c[i]]+w[i]}

背包部分内容参考自“背包问题九讲”,在此表示感谢!

若想了解更多关于背包问题的算法,可以直接搜索该资料。

2019.10.23
plussone

你可能感兴趣的:(acm)