背包问题总结

背包问题总结

一、01背包

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

思路分析

dp问题最重要的是状态转移方程。那么我们首先来定义一下状态:

dp[i][j] 表示前 i 个物品,背包容量不超过 j 时的最大价值。

那么要怎么更新状态呢?

  • 当背包容量不够时(j < v[i] ) 没得选,所以前 i 个物品的最大值就是前 i-1 个物品的最大值
  • 当容量够时 (j >= v[i]) ,可以选,所以我们需要决定是否需要选。
    • 选: dp[i][j] = dp[i-1][j-v[i]]+w[i]
    • 不选:dp[i][j] = dp[i-1][j]
    • 所以我们要取max

代码如下

#include<bits/stdc++.h>

using namespace std;

const int MAXN = 1005;
int v[MAXN];    // 体积
int w[MAXN];    // 价值 
int f[MAXN][MAXN];  // f[i][j], j体积下前i个物品的最大价值 

int main() 
{
    int n, m;   
    cin >> n >> m;
    for(int i = 1; i <= n; i++) 
        cin >> v[i] >> w[i];

    for(int i = 1; i <= n; i++) 
        for(int j = 1; j <= m; j++)
        {
            //  当前背包容量装不进第i个物品,则价值等于前i-1个物品
            if(j < v[i]) 
                f[i][j] = f[i - 1][j];
            // 能装,需进行决策是否选择第i个物品
            else    
                f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
        }           

    cout << f[n][m] << endl;

    return 0;
}

优化版本

第二维枚举时 需要逆序

  • 在二维情况下,状态f[i][j]是由上一轮i - 1的状态得来的,f[i][j]f[i - 1][j]是独立的。而优化到一维后,如果我们还是正序,则有f[较小体积]更新到f[较大体积],则有可能本应该用第i-1轮的状态却用的是第i轮的状态。
  • 状态转移方程为 f[j] = max(f[j], f[j - v[i]] + w[i])
#include<bits/stdc++.h>
#define N 1010

using namespace std;
int n,m;
int dp[N];
int v[N],w[N];

int main()
{
    cin>>n>>m;
    for(int i = 1;i<=n;i++)
    {
        cin>>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]);

        }
    }
    cout<<dp[m];
    return 0;
}


二、完全背包问题

原题链接:3. 完全背包问题 - AcWing题库

原始版本

#include<iostream>
using namespace std;
const int N = 1010;
int f[N][N];
int v[N],w[N];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i = 1 ; i <= n ;i ++)
    {
        cin>>v[i]>>w[i];
    }

    for(int i = 1 ; i<=n ;i++)
    for(int j = 0 ; j<=m ;j++)
    {
        for(int k = 0 ; k*v[i]<=j ; k++)
            f[i][j] = max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
    }

    cout<<f[n][m]<<endl;
}

递推优化

f[i , j ] = max( f[i-1,j] , f[i-1,j-v]+w ,  f[i-1,j-2*v]+2*w , f[i-1,j-3*v]+3*w , .....)
f[i , j-v]= max(            f[i-1,j-v]   ,  f[i-1,j-2*v] + w , f[i-1,j-3*v]+2*w , .....)
由上两式,可得出如下递推关系: 
                        f[i][j]=max(f[i,j-v]+w , f[i-1][j]) 
for(int i = 1 ; i <=n ;i++)
for(int j = 0 ; j <=m ;j++)
{
    f[i][j] = f[i-1][j];
    if(j-v[i]>=0)
        f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);	//注意只有这一句与 01 背包不同!!! 参考01背包,完全背包应该还能继续优化!
}

最终版本

#include<iostream>
using namespace std;
const int N = 1010;
int f[N];
int v[N],w[N];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i = 1 ; i <= n ;i ++)
    {
        cin>>v[i]>>w[i];
    }

    for(int i = 1 ; i<=n ;i++)
    for(int j = v[i] ; j<=m ;j++)
    {
            f[j] = max(f[j],f[j-v[i]]+w[i]);
    }
    cout<<f[m]<<endl;
}


三、多重背包

原题链接:4. 多重背包问题 I - AcWing题库

首先我们知道,对于完全背包问题有:

dp[i][j] = max(dp[i-1][j],dp[i-1][j-v]+w,dp[i-1][j-2v]+2w,dp[i-1][j-3v]+3w,...);
dp[i][j-v]=max(		  	  dp[i-1][j-v],  dp[i-1][j-2v]+ w,dp[i-1][j-3v]+2w,...);

进一步可以得到:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-v]+w)

那么我们按照套路,写一下多重背包dp[i][j]

dp[i][j] = max(dp[i-1][j],dp[i-1][j-v]+w,dp[i-1][j-2v]+2w,dp[i-1][j-3v]+3w,...,dp[i-1][j-sv]+sw);
dp[i][j-v]=max(			  dp[i-1][j-v],	 dp[i-1][j-2v]+ w,dp[i-1][j-3v]+2w,...,dp[i-1][j-v-(s-1)v]+(s-1)w,dp[i-1][j-sv]+(s+1)w);

观察dp[i][j]dp[i][j-v] 可知,dp[i][j] 并不能直接用 dp[i][j-v]来求得(因为项数不同)。

但是,如若仔细观察一下,还是可以发现规律的:

dp[i][j] 的最大值,取决于dp[i-1][j] 和它前面s个dp[i-1][r]

也就是下图所示:
在这里插入图片描述

那么对于每个下标,我们只需要利用滑动窗口进行遍历,求最大值即可!!!

代码

#include<bits/stdc++.h>

using namespace std;

const int N = 20005;
int n,m;	//n表示物品种数,m表示背包容量
int dp[N],q[N],g[N];//dp[i]表示体积为i时,最大价值。q[]是优先队列(用于滑动窗口的存放),存的是下标。g[]是拷贝数组,用于记录dp[i-1]这一行(把f[i][j]压缩到dp[j]之后,由于滑动窗口需要从前往后遍历,所以无法利用从后往前遍历的思想来处理f[i-1][]的数据。就用g[],把它记录下来。)

int main()
{
	cin>>n>>m;
	for(int i = 1;i<=n;i++)
	{
		int v,w,s;	
		cin>>v>>w>>s;
		memcpy(g,dp,sizeof dp);	//拷贝数组
		for(int j = 0;j<v;j++)	//这里j表示遍历到的体积k一直减去物品的体积v的余数(0<= j < v)
		{
			int hh=0,tt=-1;		//表示队列的头和尾
			for(int k=j; k<=m; k+=v)	//k表示体积(下标),每次 += v(也就是新加一个物品)
			{	
				if(hh<=tt && k-q[hh]+v>(s+1)*v)	//如果队列不空,且队列长度大于 s+1 (注意,单位长度是v),就把队列后移。
					hh++;	
				if(hh<=tt)		//如果队列不空,就求dp[k]
					dp[k] = max(dp[k],g[q[hh]]+(k-q[hh])/v *w);	//dp[k] 是滑动窗口的最大值(头部,也就是g[q[hh]],还要加上(k-q[hh])/v *w,也就是后面的偏移量)
                
				while(hh<=tt && g[q[tt]]<g[k]-(k-q[tt])/v*w)	//如果队列不空,且队尾小于下一个数,那么要把队尾替换掉
					tt--;
				q[++tt]=k;	//更新队尾
			}	
		}		
	}	
	cout<<dp[m];
	return 0;
}


四、分组背包

原题链接:9. 分组背包问题 - AcWing题库

因为每组只能选择一个,这一点和01背包是有点像的,下面给出代码及优化

#include<bits/stdc++.h>
using namespace std;

const int N=110;
int f[N][N];  //只从前i组物品中选,当前体积小于等于j的最大值
int v[N][N],w[N][N],s[N];   //v为体积,w为价值,s代表第i组物品的个数
int n,m,k;

int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>s[i];
        for(int j=0;j<s[i];j++){
            cin>>v[i][j]>>w[i][j];  //读入
        }
    }

    for(int i=1;i<=n;i++){
        for(int j=0;j<=m;j++){
            f[i][j]=f[i-1][j];  //不选
            for(int k=0;k<s[i];k++){	//意思是,把组内的所有物品都试一下,找到一个价值最大的
                if(j>=v[i][k])     f[i][j]=max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);  
            }
        }
    }
    cout<<f[n][m]<<endl;
}

优化代码

#include<bits/stdc++.h>

using namespace std;

const int N = 1010;
int dp[N];
int n, m;
int v[N][N], w[N][N], s[N];

int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		cin >> s[i];
		for (int j = 1; j <= s[i]; j++)
		{
			cin >> v[i][j] >> w[i][j];
		}
	}

	for (int i = 1; i <= n; i++)
	{
		for (int j = m; j >= 0; j--)
		{
			for (int k = 1; k <= s[i]; k++)
			{
				if (j >= v[i][k])
				{
					dp[j] = max(dp[j], dp[j - v[i][k]] + w[i][k]);
				}
			}
		}
	}

	cout << dp[m];
	return 0;
}


五、二维费用背包问题

原题链接:1022. 宠物小精灵之收服 - AcWing题库

代码

#include<bits/stdc++.h>

using namespace std;

const int N = 1010;
int n, m, k;
int dp[N][N];	//因为既要考虑收服精灵所消耗的精灵球的个数,又要考虑收服精灵所受到的伤害,所以要用二维数组表示,dp[i][j]表示最多消耗i个精灵球(可能不到i),最多收到j个伤害(可能不到j)时,所能收服的小精灵的最大数量

int main()
{
	cin >> n >> m >> k;
	for (int i = 1; i <= k; i++)
	{
		int num,hurt;
		cin >> num >> hurt;
		for (int a = n; a >= num; a--)	//因为对每个精灵只能选择收服或者不收服,所以可以利用01背包的思想(逆序优化)
		{
			for (int b = m; b >= hurt; b--)
			{
				//if(dp[a][b] < dp[a - num][b - hurt] + 1)
				dp[a][b] = max(dp[a][b], dp[a - num][b - hurt] + 1);//
				
			}
		}
	}
    //输出最多能收服的小精灵的数量
	cout << dp[n][m-1] << " ";	//注意这里,为什么要输出m-1?因为使得皮卡丘体力小于等于0的野生小精灵也不会被收服,所以最多只能消耗m-1的体力
	int i;
	for ( i = 0; i <= m; i++)	//当最多使用n个小精灵球时,找到能收服最多个小精灵个数 的最小的伤害。
	{
		if (dp[n][i] == dp[n][m - 1])	
			break;
	}
	cout <<m - i << '\n';
	return 0;
}


六、有依赖的背包

原题链接:10. 有依赖的背包问题 - AcWing题库

树型结构的分组背包

#include<bits/stdc++.h>

using namespace std;
const int N = 1110;

int n,m;
int v[N],w[N];      //分别表示体积和价值
vector<int> g[N];   //vector数组,用于表示g[fa].push_back(i);表示在fa节点下加入他的孩子节点
int root;           //表示根节点
int dp[N][N];       //dp[i][j] ,表示以i节点为根节点,选取他下面节点的体积,使得体积之和不超过j时,的最大价值

int dfs(int x)  //x表示当前搜索的节点,(目的是以x为根节点,选取他的孩子节点)
{
    for(int i = v[x];i<=m;i++)  //以x为根节点,因为:如果选择一个物品,则必须选择它的父节点。所以可以把父节点所有体积初始化为w[x]
    {
        dp[x][i] = w[x];
    }

    for(int i = 0;i<g[x].size();i++) //接着遍历该节点的所有子节点。
    {
        int y = g[x][i];
        dfs(y);         //把每一个子节点都当做父节点,递归搜索,直到叶子结点,然后返回

        for(int j = m;j>=v[x];j--)      //j表示分给以x为根节点的树的体积。。。选取子节点的总体积不超过 m,因为该节点体积为v[x],所以大于v[x]
        {
            for(int k = 0;k<=j - v[x] ;k++) //k表示选取的子树的体积范围,最大是j-v[x]。
            {
                dp[x][j] = max(dp[x][j], dp[x][j-k] + dp[y][k]);
            }
        }
    }
}
int main()
{
    cin>>n>>m;
    int fa;
    for(int i = 1;i<=n;i++)
    {
        cin>>v[i]>>w[i]>>fa;
        if(fa == -1)
        {
            root = i;
        }
        g[fa].push_back(i);     
    }
    dfs(root);  //以根节点为起始点,开始搜索。

    cout<<dp[root][m];
    return 0;
}


七、背包问题求方案数

原题链接:11. 背包问题求方案数 - AcWing题库

#include<bits/stdc++.h>

using namespace std;
const int N = 1010;
const int mod = 1e9 + 7;

int n, m;
int v[N], w[N];
int dp[N]; //dp[j]表示体积不超过j时的最大价值
int res[N];	//res[j] 表示体积不超过j时能得到最大价值的方案数

int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		cin >> v[i] >> w[i];
	}
	for(int i = 0;i<=m;i++)
	    res[i]=1;
	for (int i = 1; i <= n; i++)
	{
		for (int j = m; j >= v[i]; j--)
		{
			int value = dp[j - v[i]] + w[i];
			if (value > dp[j])	//说明value才是最大价值,所以res[j]此时的最大价值的方案数就是value对应的res[]也就是res[j-v[i]]
			{
				dp[j] = value;
				res[j] = res[j - v[i]];
			}
			else if (value == dp[j])	//如果value==dp[j],说明j 和 j-v[i]对应的最大价值是一样的,也就是说他们都是能够达到最大价值的方案,所以要加起来(注意取模)
			{
				res[j] = (res[j] + res[j - v[i]]) % mod;
			}
		}
	}
	cout << res[m]%mod;
	return 0;
}


八、背包问题求具体的方案

原题链接:12. 背包问题求具体方案 - AcWing题库

因为要保留每一次更新状态时的方案,也就是第一层循环的 i ,所以我们就不能把dp数组进行一维优化了。

至于为什么dp时,第一层循环要倒着来,我也没有想到如何定量的解释,下面来定性的分析一下:

因为dp取max时会实时更新的,也就是说,当前面的和后面的数都满足条件时,dp会保留后面的值。

因为我们要求的是字典序最小的一个方案。肯定是想要dp保留第一个满足的方案。所以我们就把它倒着遍历: for(int i = n;i>=1;i--) 这样的话,靠前面的dp[i][] 保留的就是最先满足的方案(也就保证了字典序最小)

#include<bits/stdc++.h>

using namespace std;

const int N = 1010;
int n, m;
int v[N], w[N];
int dp[N][N];

int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		cin >> v[i] >> w[i];
	}

	for (int i = n; i >= 1; i--)
	{
		for (int j = 0; j <= m; j++)
		{
			dp[i][j] = dp[i + 1][j];
			if (j >= v[i])
			{
				dp[i][j] = max(dp[i][j], dp[i + 1][j - v[i]] + w[i]);
			}

		}
	}
	
	int j = m;
	for (int i = 1; i <= n; i++)
	{
		if (j >= v[i] && dp[i][j] == dp[i+1][j-v[i]]+w[i]) //如果可以选择第i个,并且决定选择了第i个,那么就把 i 输出
		{
			cout << i << " ";	
			j -= v[i];
		}

	}
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/478396.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Windows server 2008 R2共享文件配置和web网站的发布 试题一(Windows部分)

Windows server 2008 R2共享文件配置和web网站的发布 试题一&#xff08;Windows部分&#xff09; 设置虚拟机与本机互通设置虚拟机IP关闭虚拟机防火墙设置本机IP测试本机与虚拟机是否可以互通 开启文件共享function discovery resource publication服务的开启SSDP Discovery服…

SpringBoot-03 | SpringBoot自动配置

SpringBoot-03 | SpringBoot自动配置 原理分析代码示例源码剖析SpringBootConfiguration&#xff1a;组合注解&#xff0c;标记当前类为配置类ComponentScanEnableAutoConfigurationImport加载spring.factoriesrun初始化加载spring.factoriesspring.factories中的钩子类 网上盗…

【最后2天】京东云游戏云服务器0门槛抽奖送!云服务器选购推荐 京东云 阿里云 腾讯云对比 幻兽帕鲁 雾锁王国 省钱学生党

好消息&#xff1a;抽奖活动开启&#xff01;时间&#xff1a;3月17日——3月24日 最高奖品&#xff1a;16G 6个月&#xff1b;32G 3个月 抽奖规则&#xff1a;B站点赞评论关注即可参与抽奖&#xff0c;3.24日公布获奖名单。 抽奖地址&#xff1a; 【首次抽奖】16G、32G免费…

每日学习笔记:C++ STL 容器的杂谈

三种自定义STL容器 string作为STL容器 C风格数组作为STL容器 C11以后 C11以前 容器元素类型是引用 使用智能指针存储元素 使用引用外覆器 各容器使用时机 如何分别用两种不同的排序准则来存储同批数据&#xff1f; 解决方案&#xff1a;将容器元素改为智能指针即可。 根据排…

CentOS/RHEL 6.5 上 NFS mount 挂起kernel bug

我本身有四台机器做WAS集群&#xff0c;挂载nfs&#xff0c;其中随机一台客户端计算机端口关闭释放将进入不良状态&#xff0c;对 NFSv4 挂载的任何访问都将挂起&#xff08;例如“ls&#xff0c;cd 或者df均挂起”&#xff09;。这意味着没有人并且所有需要访问共享的用户进程…

【笔记】以论文发表形式通俗理解 TCP/IP模型

【笔记】以论文发表形式通俗理解 TCP/IP模型 前言TCP/IP模型理论通俗理解 前言 在网络基础学习过程中&#xff0c;以前只对TCP/IP理解个字面&#xff0c;网上查一下能知道个字面意思&#xff0c;但是连起来到底是什么意思&#xff0c;还是一知半解的&#xff0c;停留在表面&am…

实型数据详解

1 实型常量的表示方法 实数(real number)又称浮点数(floating-point number)。实数有两种表示形式: (1)十进制小数形式。它由数字和小数点组成(注意必须有小数点)。.123、123.、123.0、0.0都是十进制小数形式。 (2)指数形式。如123e3或123E3都代表123x103。但注意字母e(或E)…

gdb和makefile的讲解

Linux调试器-gdb使用 gdb可以用于Linux环境下的程序的调试&#xff0c;就例如vs环境下的打断点&#xff0c;然后逐步分析语句等 1 gdb的背景 程序的发布方式有两种&#xff0c;debug模式和release模式 我们在使用vs21时大家都清楚&#xff0c;release版本是不能被调试的&…

MySQL定时任务Event详解

文章目录 基本概念一、Event事件使用权限二、开启\关闭Event事件三、Event事件定义格式四、事件调度使用案例4.1 准备工作4.2 创建单次定时执行事件4.2.1 创建指定时间单次执行事件任务4.2.2 创建延迟时间单次执行事件任务4.2.3 创建单次执行事件任务[多SQ执行] 4.3 创建循环定…

【机器学习】一文搞懂算法模型之:Transformer

Transformer 1、引言2、Transformer2.1 定义2.2 原理2.3 算法公式2.3.1 自注意力机制2.3.1 多头自注意力机制2.3.1 位置编码 2.4 代码示例 3、总结 1、引言 小屌丝&#xff1a;鱼哥&#xff0c; 你说transformer是个啥&#xff1f; 小鱼&#xff1a;嗯… 啊… 嗯…就是… 小屌…

uni-app攻略:如何对接驰腾打印机

一.引言 在当前的移动开发生态中&#xff0c;跨平台框架如uni-app因其高效、灵活的特点受到了开发者们的青睐。同时&#xff0c;随着物联网技术的飞速发展&#xff0c;智能打印设备已成为许多业务场景中不可或缺的一环。今天&#xff0c;我们就来探讨如何使用uni-app轻松对接驰…

异步爬虫实践攻略:利用Python Aiohttp框架实现高效数据抓取

在当今信息爆炸的时代&#xff0c;数据是无处不在且变化迅速的。为了从海量数据中获取有用的信息&#xff0c;异步爬虫技术应运而生&#xff0c;成为许多数据挖掘和分析工作的利器。本文将介绍如何利用Python Aiohttp框架实现高效数据抓取&#xff0c;让我们在信息的海洋中快速…

怎么才可以实现自定义异常?

在回答怎么才可以自定义异常这个问题之前&#xff0c;我们先看异常处理对象是怎么实现的&#xff1f;下图为运行时异常需要继承 RuntimeException异常类。 而RuntimeException异常类又继承Exception异常类。 所以&#xff0c;要实现自定义异常类&#xff0c;就需要去继承Runtim…

matlab矩形薄板小挠度弯曲有限元编程 |【Matlab源码+理论文本】|板单元| Kirchoff薄板 | 板壳单元

专栏导读 作者简介&#xff1a;工学博士&#xff0c;高级工程师&#xff0c;专注于工业软件算法研究本文已收录于专栏&#xff1a;《有限元编程从入门到精通》本专栏旨在提供 1.以案例的形式讲解各类有限元问题的程序实现&#xff0c;并提供所有案例完整源码&#xff1b;2.单元…

2024年三大主食冻干品牌测评,希喂、SC、K9自费实测综合PK

在现今注重科学养宠的时代背景下&#xff0c;主食冻干已经成为猫咪日常饮食的重要组成部分。主食冻干的高肉含量特性使其易于被猫咪吸收和消化&#xff0c;同时&#xff0c;它还能提供其他猫粮所无法提供的微量物质&#xff0c;满足猫咪的全面营养需求。然而&#xff0c;在众多…

力扣题目训练(23)

2024年2月16日力扣题目训练 2024年2月16日力扣题目训练645. 错误的集合653. 两数之和 IV - 输入二叉搜索树657. 机器人能否返回原点307. 区域和检索 - 数组可修改309. 买卖股票的最佳时机含冷冻期174. 地下城游戏 2024年2月16日力扣题目训练 2024年2月16日第二十三天编程训练&…

【开发环境搭建篇】Redis客户端安装和配置

作者介绍&#xff1a;本人笔名姑苏老陈&#xff0c;从事JAVA开发工作十多年了&#xff0c;带过大学刚毕业的实习生&#xff0c;也带过技术团队。最近有个朋友的表弟&#xff0c;马上要大学毕业了&#xff0c;想从事JAVA开发工作&#xff0c;但不知道从何处入手。于是&#xff0…

AST学习入门

AST学习入门 1.AST在线解析网站 https://astexplorer.net/ 1.type: 表示当前节点的类型&#xff0c;我们常用的类型判断方法t.is********(node)**,就是判断当前的节点是否为某个类型。 2**.start**:表示当前节点的开始位置 3.end:当前节点结束 4.loc : 表示当前节点所在的行…

Qt利用反射机制实现函数调用

QT本身就带有强大的反射功能&#xff0c;如果想通过函数名称字符串调用函数&#xff0c;需要在被调用的函数前添加宏&#xff1a;Q_INVOKABLE 父类 QtInvoke.h 头文件&#xff1a; #pragma once #include <QMainWindow> #include "ui_QtInvoke.h" class Qt…

麒麟系统中使用nginx发布项目

1. 安装Nginx sudo apt-get update #进行所有安装操作前都要执行这一句 sudo apt install nginx #出现询问就Yes参考具体 Nginx—在linux的ubuntu系统上的安装使用 2. 修改发布文件 将打包好的dist文件夹中的所有文件覆盖下面这个文件夹中的所有文件 如果出现没有权限替…