背包九讲学习体会

参考博客https://blog.csdn.net/yoer77/article/details/70943462

以下都是按上面链接中博客学习过程的心得总结


1、第一部分

递推公式中,绝不能使用未定义的值(相当于等式后面的东西在上几次被赋值过)

递归公式中,从大到小递归,得到最小解后回溯,因此合法。但是在递推公式中,若也是从大到小推,如

dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]]+v[i]) 则出现dp[i-1][j]未被定义的状态,不合法。这也就是为什么顺推的原因


2、压缩空间

由于dp[i][j] 的值仅仅受到dp[i-1] 的影响, 因此压缩成一维数组.

以轮次去理解

扫描二维码关注公众号,回复: 2444130 查看本文章
for i = 1 to n
    for j = W to w[i]
        dp[j] = max(dp[j], dp[j-w[i]] + v[i])

逆推的原因,等式右边的值,相当于dp[i-1][j]和dp[i-1][j-w[i]]。为什么?因为执行过程中,右边dp[j]和dp[i-w[i]] 保留的是上轮的值。试想若是顺推,那么本轮用到的值,就是本轮修改过的,那么就和上轮无关了


从此开始讨论的递推都是一维数组形式

dp[j]的意义,也就是容量为j的时候,取得最大的价值


3、细节讨论,背包最终是否装满

通过初始化来区分,初始化相当于确立合法的解。

1)若要求背包恰好装满,则只初始化dp[0]=0,其他dp[other] = -1。也就是不合法状态,你并不知道这种容量是否能由条件刚好凑出来,因为没装满不合法。而容量是0的时候价值是0,就符合恰好装满条件,而其他的解必定是由之前的合法解推出来的,因此在初始化的时候确定为-1。

2)若可以不正好装满,则统统初始化成0。因为任何容量的背包都是合法的。

#include <iostream>
#include <cstring>
#define MAXN 10000
using namespace std;

int dp[MAXN];
int w[MAXN] = {0, 2, 1, 3, 2};
int v[MAXN] = {0, 3, 2, 4, 2};
int W = 5, n = 4;

int solve(int n, int W) {
    memset(dp, 0, sizeof(dp));
    for (int i = 1; i <= n; i++) { // i从1开始,递增
        for (int j = W; j >= 0; j--) { // j按递减顺序填表
            if (j < w[i]) {
                dp[j] = dp[j];
            }
            else {
                dp[j] = max(dp[j], dp[j-w[i]] + v[i]);
            }
        }
    }
    return dp[W];
}

int main() {
    cout << solve(n, W) << endl;
    return 0;
}


4、完全背包问题(每个物品数量不限)

分成两种情况

1)不选i dp[i][j] = dp[i-1][j]

2)选i dp[i][j] = dp[i][j-w[i]]+v[i]

解释:由于不知道到底装几个i,因此先装入一个,把问题转化成装入一个i后的最大值情况(状态转移了)


5、完全背包空间压缩

for i = 1 to n
    for j = 0 to W
        dp[j] = max(dp[j], dp[j-w[i]] + v[i])

为什么是顺推?文章原话 完全背包问题第二层的j正序循环,因为dp[i][j] 需要用到dp[i][jw[i]] , 只用一维数组保存的话,按j正序循环,就能保证计算dp[j] 时,旧的dp[j] 的值就等于dp[i1][j] 的值,dp[jw[i]] 已经计算过,且对应的就是二维数组中dp[i][jw[i]] 的值。


个人理解:公式中dp[j] = max(dp[j], dp[j-w[i]] + v[i]), 等式右边的dp[j] 实际上是上一轮的dp[i-1][j] , 由于是顺推dp[j-w[i]]也是已经被计算出来的。

#include <iostream>
#include <cstring>
#define MAXN 1000
using namespace std;

int dp[MAXN];
int w[MAXN] = {0, 3, 4, 2};
int v[MAXN] = {0, 4, 5, 3};
int W = 7, n = 3;

int solve(int n, int W) {
    memset(dp, 0, sizeof(dp));
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= W; j++) {
            if (j < w[i]) {
                dp[j] = dp[j];
            }
            else {
                dp[j] = max(dp[j], dp[j-w[i]] + v[i]);
            }
        }
    }
    return dp[W];
}

int main() {
    cout << solve(n, W) << endl; // 10
    return 0;
}

6、多重背包

相当于才成很多个01背包,第i 个物品有 mi 个, 等价于有mi 个相同的物品。但直接拆分成 mi 件物品并不是最好的方法。我们可以利用二进制来拆分。例如 m1=13=20+21+22+6 ,我们将第一种物品共13件,拆分成 20,21,22,6 这四件

const int N = 100, W = 100000;
int cost[N], weight[N], number[N];
int dp[W + 1];

int knapsack(int n, int w)
{
    for (int i = 0; i < n; ++i)
    {
        int num = min(number[i], w / weight[i]);//每个物品最多能有num个
         for (int k = 1; num > 0; k*=2)//拆成2^0 , 2^1, 2^2...也是选和不选两种抉择
        {
            if (k > num) k = num;//k就是2^0.2^1....
            num -= k;
            for (int j = w; j >= weight[i] * k; --j)//01背包,把k个物件当成一个整体
                dp[j] = max(dp[j], dp[j - weight[i] * k] + cost[i] * k);
        }
    }
    return  dp[w];
}

7、优化多重背包


(1)单调队列的概念。https://blog.csdn.net/justmeh/article/details/5844650
例题 滑动窗口,给定序列,和窗口大小k,求每次滑动的时候窗口内最大值

队首存放value最大的,向队尾递减。加入一个新元素时,执行两步操作 1)队尾中有小于本次值的,全出队 2)队首中,有下标已经小于本次窗口l的(已经在窗口之外了) 出队。 然后输出队首的值。

代码如下

#include<iostream>
#include<queue>

using namespace std;

struct Node
{
	int val;
	int index;
};

void GetMax(int *numSequence,int len, int *result,int k)
{
	Node *que = new Node[len];
	int head = 0;
	int end = 0;

	for(int i=0;i<len;i++)
	{
		Node tmp;
		tmp.val = numSequence[i];
		tmp.index = i;

		while(end!=0 && que[end].val<=numSequence[i])
			--end;
		++end;
		que[end] = tmp;

		while(end!=0 && que[head].index<i-k+1)
			++head;
		result[i] = que[head].val;
	}	
	delete []que;
}

int main()
{
	int len, k;
	cin>>len>>k;

	int *numSequence = new int[len];
	int *maxResult = new int[len];

	for(int i=0;i<len;i++)
		cin>>numSequence[i];

	GetMax(numSequence,len,maxResult,k);

	for(int i=k-1;i<len;i++)
		cout<<i<<": "<<maxResult[i]<<endl;

	delete[]numSequence;
	delete[]maxResult;
	numSequence = NULL;
	maxResult = NULL;

	return 0;
}

2)

猜你喜欢

转载自blog.csdn.net/qq_37591656/article/details/80031547