zoukankan      html  css  js  c++  java
  • 背包九讲

    视频链接:背包九讲专题_哔哩哔哩_bilibili

    一,01 背包问题

    题目链接:2. 01背包问题 - AcWing题库

    题解:

    二维代码:

      f [ i ][ j ] 表示只考虑到前 i 个物品,且总体积恰小于等于 j 的情况下,总价值最大是多少

      递推式:

        情况①:不选第 i 个物品,f [ i ][ j ] = f [ i - 1 ][ j ] 

        情况②:选第 i 个物品,f [ i ][ j ] = f [ i - 1 ][ j - v[i] ] + w[ i ]

        f [ i ][ j ] = max(①,②)

    #define _CRT_SECURE_NO_WARNINGS
    #include<stdio.h>
    #include<stdlib.h>
    #define N 1010
    int n, m;    // 物品数,背包体积
    int f[N][N];
    int v[N], w[N]; // 物品体积,物品价值
    int max(int a, int b)
    {
    	return a > b ? a : b;
    }
    int main(void)
    {
    	scanf("%d%d", &n, &m);
    	for (int i = 1; i <= n; i++)
    		scanf("%d%d", &v[i], &w[i]);
    
    	for (int i = 1; i <= n; i++)
    	{
    		for (int j = 1; j <= m; j++)
    		{
    			f[i][j] = f[i - 1][j]; // 默认不选
    			if (j >= v[i]) // 如果可以选,即背包体积大于物品体积
    				f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
    		}
    	}
    
    	printf("%d
    ", f[n][m]);
    
    	system("pause");
    	return 0;
    }

    解析:

    			f[i][j] = f[i - 1][j]; // 默认不选
    			if (j >= v[i]) // 如果背包体积大于物品体积
    				f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);

    上面这段代码就是从第 i-1 个物品的状态,推导第 i 个物品的状态的递推式。相较于上文 “选于不选” 的判断条件,这里省略了 “不选“ 的判断条件,并在可以选择的时候,比较选与不选各自的价值。也可以理解为:不能选的话,只能不选;可以选的话,就要看选与不选哪个价值大咯。

    二维压缩为一维:01 背包的空间优化

      f [ j ] 表示 如果当前考虑的是第 i 个物品,那么在总体积小于等于 j 的情况下,只考虑到前 i - 1 个物品的总价值最大是多少

      递推式:从第 i-1 个物品的状态,推导出第 i 个物品的状态

        情况①:不选第 i 个物品,f [ j ] = f [ j ] ,(注意,虽然这里等式左右一样,但意义不一样,等式左边代表的是只考虑到前 i  个物品,而等式右边代表的是只考虑到前 i - 1 个物品。下文会将这种从 只考虑到前 i - 1 个物品 到 只考虑到前 i  个物品 的变化叫刷新。这也是为什么第二层循环要从后往前循环的原因 )

        情况②:选第 i 个物品,f [ j ] = f[ j - v[i] ] + w[ i ],(注意,f [ j ] 代表的是只考虑到前 i  个物品, f[ j - v[i] ] 代表的是只考虑到前 i - 1 个物品。下文会将这种从 只考虑到前 i - 1 个物品 到 只考虑到前 i  个物品 的变化叫刷新。这也是为什么第二层循环要从后往前循环的原因 )

        f [ i ][ j ] = max(①,②)

    #define _CRT_SECURE_NO_WARNINGS
    #include<stdio.h>
    #include<stdlib.h>
    #define N 1010
    int n, m;    // 物品数,背包体积
    int f[N];
    int v[N], w[N]; // 物品体积,物品价值
    int max(int a, int b)
    {
    	return a > b ? a : b;
    }
    int main(void)
    {
    	scanf("%d%d", &n, &m);
    	for (int i = 1; i <= n; i++)
    		scanf("%d%d", &v[i], &w[i]);
    
    	for (int i = 1; i <= n; i++)
    		for (int j = m; j >= v[i]; j--)
    			f[j] = max(f[j], f[j - v[i]] + w[i]);
    
    	printf("%d
    ", f[m]);
    
    	system("pause");
    	return 0;
    }
    

    解析:

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

    上面这段代码就是从第 i-1 个物品的状态,推导第 i 个物品的状态的递推式。因为不选是 f[ j ] = f[ j ],没有变化,所以虽然这里没有表示出不选的代码,但实际上还是有这个含义的。再来说一下不选,f[ j - v[ i] ] + w[ i ],问题就出在这个 f[ j - v[ i] ] 身上,因为如果我们 j 是正常的从小到大递增的话,那么 f[ j ] 肯定 还没经过刷新,但 f[ j - v[ i] ] 就一定经过刷新,所以此时 f[ j - v[ i] ] 代表的是 “只考虑到前 i  个物品”,而不是我们需要的 “只考虑到前 i - 1 个物品”。那么如何在一次 j 循环中,保证用到的 f[ j - v[ i] ] 都没有刷新过呢?只能是从后往前循环了。 ♪(^∀^●)

    所以,内层循环从二维压缩到一维就是:

    		for (int j = m; j >= 0; j--)
    		{
    			f[j] = f[j];  // 不选
    			if(j >= v[i]) // 可以选,比较选与不选的价值
    				f[j] = max(f[j], f[j - v[i]] + w[i]);
    		}

    进一步优化就是:

    		for (int j = m; j >= v[i]; j--)
    			f[j] = max(f[j], f[j - v[i]] + w[i]);
    

      

     f[] 初始化不同引起的含义不同:

    ① 初始化为:f[ 0 ] = 0;f[ j ] = 0;

    此时 f[ j ] 代表如果当前考虑的是第 i 个物品,那么在总体积小于等于 j 的情况下,只考虑到前 i - 1 个物品的总价值最大是多少?

    注意关键词:小于等于

    ② 初始化为:f[ 0 ] = 0;f[ j ] = -inf;

     此时 f[ j ] 代表如果当前考虑的是第 i 个物品,那么在总体积恰好等于 j 的情况下,只考虑到前 i - 1 个物品的总价值最大是多少?

    注意关键词:恰好等于

    ③ 原因:

    证 :

      如果是 ① 的情况:

        1,我们可以从 f[ 0 ] = 0  ==》 f[ v[i] ] = max(f[ 0 ], w[i])   ==》 从而推导出所有 f[ j ],j <= m

        2,同样的,设有 k < m,我们也可以从 f[ k ] = 0  ==》 f[ k + v[i] ] = max(f[ k ], w[i])   ==》 从而推导出所有 f[ j ],k <= j <= m

        所以,第 2 种实际算出来的还要减去 k,才是实际物品需要占用的空间,但若存在这么两种组合,使得一种组合物品的空间少于另外一种组合物品的空间,但总价值一样且最大,则 f[ m ] 是可以由这两种状态推导出来的。

      但如果是 ② 的情况,则会因为无穷小,使得 2 的情况不会发生,使得 一定是恰好等于。

    ④ 答案取值

    如果是 ① 情况的话:f[ m ] 就是答案

    如果是 ② 情况的话:需要比较所有 f[ j ],具体原因结合 ③ 以及下面代码中给出的测试案例分析。

    #define _CRT_SECURE_NO_WARNINGS
    #include<stdio.h>
    #include<stdlib.h>
    #define N 1010
    #define inf 0x3f3f3f3f
    int n, m;    // 物品数,背包体积
    int f[N];
    int v[N], w[N]; // 物品体积,物品价值
    int max(int a, int b)
    {
    	return a > b ? a : b;
    }
    int main(void)
    {
    	scanf("%d%d", &n, &m);
    	for (int i = 1; i <= n; i++)
    		scanf("%d%d", &v[i], &w[i]);
    	for (int i = 1; i <= m; i++)
    		f[i] = -inf;
    
    	for (int i = 1; i <= n; i++)
    	{
    		for (int j = m; j >= 1; j--)
    		{
    			if(j >= v[i])
    				f[j] = max(f[j], f[j - v[i]] + w[i]);
    			printf("%d ", f[j]);
    		}puts("");
    	}
    	int res = 0;
    	for (int i = 1; i <= m; i++)
    	{
    		printf("%d
    ", f[i]);
    		res = max(res, f[i]);
    	}
    	/*
    	测试案例:
    	2 2
    	1 101
    	2 100
    	*/	
    	system("pause");
    	return 0;
    }

    ④ 小知识点:函数外面的全局变量是用堆保存的,而堆是全部初始化为 0 的

    二,完全背包问题

    三,多重背包问题

    四,混合背包问题

    五,二维费用的背包问题

    六,分组背包问题

    七,背包问题求方案数

    八,求背包问题的方案

    九,有依赖的背包问题

    ========== ========= ======== ====== ====== ===== ==== === == =

  • 相关阅读:
    spring(1)
    mybatis(7)自定义结果集(一对多/多对一)
    延迟加载
    《构建之法》阅读笔记03
    http socket
    转换
    .net后台通过xmlhttp 和远程服务通讯
    XMLHttpRequest介绍
    js 贪吃蛇
    触发器
  • 原文地址:https://www.cnblogs.com/asdfknjhu/p/15041776.html
Copyright © 2011-2022 走看看