• 动态规划之背包问题


    动态规划之背包问题

    作为算法界的经典,背包问题一直是动态规划的一个代表,也是给了无数算法新人一记迎头痛击啊,我也是被其困扰了好长一段时间,连最基础的模板都很难理解,更别说无尽的变体了,今天我就来带大家回顾一下基础模板的思路。

    01背包

    题面:

    N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

    i件物品的体积是 v[i],价值是 w[i]

    求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

    输出最大价值。

    代码:

    int main()
    {
        int n, m, v[1010] = { 0 }, w[1010] = { 0 }, dp[1010]= { 0 };
        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--)
                dp[j] = max (dp[j], dp[j - v[i]] + w[i]);
        printf("%d", dp[m]);
        return 0;
    }
    

    题解:

    状态转移方程:dp[j] = max (dp[j], dp[j - v[i]] + w[i]);

    考虑前i个物品在空间为j的情况下的最优解,要加入当前物品需要预留空间,则将当前与腾出空间后加上当前物品价值对比得出当前最优解。

    值得注意的是第二层j循环从大到小遍历直到不可能装下当前物品,若从小到大则可能会发生前面的情况影响后面的情况,导致一件物品被装入多次,而从大到小则不会。


    二维01背包

    题面:

    洛谷 P1507 NASA的食物计划

    航天飞机的体积有限,当然如果载过重的物品,燃料会浪费很多钱,每件食品都有各自的体积、质量以及所含卡路里。在体积和质量的最大值的情况下,请输出能达到的食品方案所含卡路里的最大值,当然每个食品只能使用一次。

    代码:

    int main()
    {
    	int H, T, n;
    	scanf("%d %d", &H, &T);
    	scanf("%d", &n);
    	int h[60], t[60], k[60], dp[500][500] = { 0 };
    	for (int i = 1; i <= n; i++) 	
    	{
    		scanf("%d %d %d", &h[i], &t[i], &k[i]);
    		for (int j = H; j >= h[i]; j--)
    			for (int m = T; m >= t[i]; m--)
    				dp[j][m] = max(dp[j][m], dp[j - h[i]][m - t[i]] + k[i]);
    	}
    	printf("%d", dp[H][T]);
    	return 0;
    }
    

    题解:

    状态转移方程:dp[j][m] = max(dp[j][m], dp[j - h[i]][m - t[i]] + k[i]);

    在01背包的基础上,dp数组多开一维度,多进行一层循环,在这题中,dp[j][m]j表示体积,m表示质量。

    同样是从大到小遍历防止同一个食物放入两次。


    完全背包

    题面

    N 件物品和一个容量是 V 的背包。每件物品可无限使用。

    i件物品的体积是 v[i],价值是 w[i]

    求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

    输出最大价值。

    代码:

    int main()
    {
        int n, m, v[1010] = { 0 }, w[1010] = { 0 }, dp[1010]= { 0 };
        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 = 0; j printf("%d", dp[m]);
        return 0;
    }
    

    题解:

    状态转移方程:dp[j] = max (dp[j], dp[j - v[i]] + w[i]);

    和01背包不同,第二层j循环从小到大,用多装的情况覆盖少的情况,正好可以考虑无限次数的使用。


    多重背包

    题面

    N种物品和一个容量是V的背包。

    第i种物品最多有s[i]件,每件体积是v[i],价值是w[i]

    求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。

    输出最大价值。

    代码:

    int main()
    {
    	int n, m, dp[1010] = { 0 }, s[1010] = { 0 }, w[1010] = { 0 }, v[1010] = { 0 };
    	scanf("%d %d", &n, &m);
    	for (int i = 1; i <= n; i++) scanf("%d %d %d", &v[i], &w[i], &s[i]);
    	for (int i = 1; i <= n; i++)
    		for (int j = m; j >= 1; j--)
    			for (int k = 0; k * v[i] <= j && k <= s[i]; k++)
    				dp[j] = max(dp[j], dp[j - k * v[i]] + k * w[i]);
    	printf("%d", dp[m]);
    	return 0;
    }
    

    题解:

    状态转移方程:dp[j] = max(dp[j], dp[j - k * v[i]] + k * w[i]);

    多重背包类似01背包,将多个相同的物体拆开来考虑,我们只需要在01背包的基础上,加入第三层循环,来考虑每个物体放入的个数,即可完成此题,但当数据量过大时,O(n³)的复杂度显然是难以接受的,所以在多重背包我们往往会用到二进制优化


    多重背包——二进制优化

    题面

    N种物品和一个容量是V的背包。

    第i种物品最多有s[i]件,每件体积是v[i],价值是w[i]

    求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。

    输出最大价值。

    代码:

    int main()
    {
    	int n, m, dp[2010] = { 0 }, w[2010] = { 0 }, v[2010] = { 0 };
    	int a, b, c, count = 1;
    	scanf("%d %d", &n, &m);
    	for (int i = 1; i <= n; i++) 
    	{
    		scanf("%d %d %d", &a, &b, &c);
    		for (int j = 1; c > j; j *= 2)
    		{
    			v[count] = j * a;
    			w[count] = j * b;
    			count++;
    			c -= j;
    		}
    		v[count] = c * a;
    		w[count] = c * b;
    		count++;
    	}
    	count--;
    	for (int i = 1; i <= count; i++)
    		for (int j = m; j >= v[i]; j--)
    			dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
    	printf("%d", dp[m]);
    	return 0;
    }
    

    题解:

    状态转移方程:dp[j] = max(dp[j], dp[j - v[i]] + w[i]);

    根据二进制的性质,我们完全可以用二进制的形式来表达一个数,或者更简单,我们只需要用二进制表达其中的一部分,使得比这个数小的任何数都能被这几个部分组成即可,这样,我们就通过数个组分的组合,完成了所有可能的数量。

    如上,我们用若干偶数和一个余数,将所有的物体给拆分为若干组分,每个组分的体积和质量的都是单个物体的若干份,这样,我们就将多重背包转换为了01背包。


    分组背包

    题面

    N种物品和一个容量是V的背包。

    每组物品有若干个,同一组内的物品最多只能选一个。

    每件物品的体积是v [i][j],价值是w[i][j],其中i是组号j是组内编号。。

    求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。

    输出最大价值。

    代码:

    int main()
    {
    	int n, m,s, v[1010] = { 0 }, w[1010] = { 0 }, dp[1010] = { 0 };
    	scanf("%d %d", &n, &m);
    	for (int i = 1; i <= n; i++)
    	{
    		scanf("%d", &s);
    		for (int j = 1; j <= s; j++) scanf("%d %d", &v[j], &w[j]);
    		for (int j = m; j > 0; j--)
    			for (int k = 1; k <= s; k++)
    				if (j >= v[k])
    					dp[j] = max(dp[j], dp[j - v[k]] + w[k]);
    	}
    		printf("%d", dp[m]);
    	return 0;
    }
    

    题解:

    状态转移方程:dp[j] = max(dp[j], dp[j - v[k]] + w[k]);

    分组背包与01背包类似,我们分组考虑,在某一组考虑完之后我们再进入下一组,第二层循环从大到小遍历直到不可能装下当前组物品,因为从小到大装会导致同一组装入多个物品,已经装入的东西会影响后面的情况。


    总结

    以上便是背包问题模板的总结,本文由凉茶coltea撰写,转载请注明出处。请注明出处。

  • 相关阅读:
    Java面向对象学习笔记-2
    谣言检测论文精读——2.A Convolutional Approach for Misinformation Identification
    聚观早报|中国制造成世界杯交通主力;特斯拉拟召回32万辆车
    我幼儿园的弟看了都直呼简单的【栈和队列】
    2023最新SSM计算机毕业设计选题大全(附源码+LW)之java智慧社区家政服务系统80q7o
    perf工具与perf report children的含义
    《style scope》 作用域保护如何修改(组件库)子组件的样式
    计算机操作系统第二章 进程的描述与控制
    spark向hadoop写入文件后,查路径为目录,无法查值
    MongoDB简介
  • 原文地址:https://www.cnblogs.com/coltea/p/18038584