首页 > 其他分享 >动态规划之——背包DP(进阶篇)

动态规划之——背包DP(进阶篇)

时间:2024-08-06 19:56:43浏览次数:16  
标签:多重 背包 int cin 进阶篇 DP 数组 模板

文章目录

概要说明

本文讲多重背包、混合背包以及二维费用背包,至于其他背包问题后续补充
01背包和完全背包问题:传送门

多重背包(朴素算法)

模板例题

点这里~~

思路

多重背包也是 0-1 背包的一个变式,与 0-1 背包的区别在于每种物品有 k 个,而非一个
那么我们可以在01背包的基础上,多加上一重循环,让循环遍量k从1开始循环,循环到总重W即可

code

void solve(){
	int n,W;
	cin >> n >> W;
	for(int i=1;i<=n;++i){
		cin >> w[i] >> v[i] >> s[i];
	}
	for(int i=1;i<=n;++i)
	   for(int j=W;j>=w[i];--j)
	      for(int k=1;k*w[i]<=j && k<=s[i];++k){
	      	f[j]=max(f[j],f[j-k*w[i]]+k*v[i]);
		  }
    cout << f[W];
	return ;
}

很明显,这个代码的时间复杂度为 O ( n W k ) O(nWk) O(nWk)
当数据过大时,这个代码很容易超时
因此我们也就衍生出了二进制优化多重背包的算法

多重背包(二进制优化)

模板例题

点这里~~

思路

我们首先确认三点:

  • 我们知道转化成01背包的基本思路就是:判断每件物品我是取了你好呢还是不取你好
  • 我们知道任意一个实数可以由二进制数来表示,也就是 2 0 2 k 2^0 2^k 202k其中一项或几项的和
  • 这里多重背包问的就是每件物品取多少件可以获得最大价值

为什么可以进行二进制优化,我们拿苹果进行举例:
比如苹果总数为50,如果我们一个个拿,需要拿50次
假设说这时候有6个箱子,这6个箱子的容量为 1 , 2 , 4 , 8 , 16 , 19 1,2,4,8,16,19 1,2,4,8,16,19(为什么有19呢,因为剩下的总数不够32,所以将剩余的苹果单独拎出来)
那么我们就可以将这50个苹果分别装在这6个箱子里面,是不是比朴素的算法快很多?

二进制优化多重背包本质上就是将个数进行拆分,由于每个数都能用二进制数来表示,所以它的时间复杂度可以降低到 O ( n W l o g 2 s ) O(nWlog_2s) O(nWlog2​s)
基于上述思想,我们只需要将每次拆分的物品个数和价值存入到数组中,最后套用01背包的模板即可

code

const int N=1e4+1010,M=2e3+5;
int v[N],w[N],f[M];
void solve(){
	int n,W;
	cin >> n >> W;
	int cnt=1;//统计数量
	for(int i=1;i<=n;++i){
		int a,b,s;
		cin >> a >> b >> s;
		for(int j=1;j<=s;j<<=1){
			w[cnt]=j*a;//存入重量
			v[cnt++]=j*b;//存入价值
			s-=j;//总个数减去二进制的个数
		}
		if(s){//若还有剩余
			w[cnt]=s*a;
			v[cnt++]=s*b;
		}
	}
	for(int i=1;i<cnt;++i)  //01背包的模板
	   for(int j=W;j>=w[i];--j){
	   	f[j]=max(f[j],f[j-w[i]]+v[i]);
	   }
    cout << f[W];
	return ;
}

二进制优化是将个数进行拆分,那么我们有没有其他更快的方法呢?
答案是有的:我们可以将体积进行拆分

多重背包(队列优化)

模板例题

点这里~~

思路

我们先来看一串数据:
在这里插入图片描述
从第二个物品开始,每个物品的价值跟物品的重量w有关系
就拿 f [ 9 ] 来说,跟他有关的是 f [ 7 ] 、 f [ 5 ] 、 f [ 3 ] f[9]来说,跟他有关的是f[7]、f[5]、f[3] f[9]来说,跟他有关的是f[7]、f[5]、f[3],这不是跟物品重量为2有很大关系吗,每次都相差物品重量的k倍关系
为什么没有1呢,因为我们可选的数量为3, 2 ∗ 3 = 6 , 而 9 > 6 2*3=6,而9>6 2∗3=6,而9>6,说明9不可能由1转换而来,这时候队首的1就需要出队
那么我们就可以考虑用滑动窗口的思想来解决队首和队尾的问题
不了解滑动窗口可以先看这篇博客:传送门
首先定义两个指针hh(队首),tt(队尾)
然后开3个数组 q , f , g , q 数组用于存下标, f 数组存答案, g 数组表示的是 f 数组每次转移的状态 q,f,g,q数组用于存下标,f数组存答案,g数组表示的是f数组每次转移的状态 q,f,g,q数组用于存下标,f数组存答案,g数组表示的是f数组每次转移的状态
当队首大于队尾时,说明队尾为空,不执行任何操作
若不为空,则需要判断当前重量k是否超出范围(个数s ∗ * ∗重量w),超出则需要将队首出队
接着判断**当前状态f[k]**是否比队首的状态( g [ q [ h h ] ] ) + ( k − q [ h h ] ) / w ∗ v ) g[q[hh]])+(k-q[hh])/w*v) g[q[hh]])+(k−q[hh])/w∗v)
k − q [ h h ] 代表剩余的体积,然后除以体积 w 乘上价值 v k-q[hh]代表剩余的体积,然后除以体积w乘上价值v k−q[hh]代表剩余的体积,然后除以体积w乘上价值v
然后判断当前状态 g [ k ] g[k] g[k]是否比队尾元素大,若为真则将他们一一排除
然后将当前元素存入q数组中
每次循环都需要将f的状态转移为g数组,因此我们需要用到memcpy函数

看的不是很明白?接下来看代码慢慢食用

int q[N],g[N],f[N];
void solve(){
	int n,W;
	cin >> n >> W;
	for(int i=1;i<=n;++i){
		memcpy(g,f,sizeof f);//状态转移
		int w,v,s;
		cin >> w >> v >> s;
		for(int j=0;j<w;++j){
			int hh=0,tt=-1;//队首和队尾的指针
			for(int k=j;k<=W;k+=w){//每次都相差w倍的关系
				if(hh<=tt && q[hh]<k-s*w) hh++;//判断队首是否需要出队
				if(hh<=tt) f[k]=max(g[k],g[q[hh]]+(k-q[hh])/w*v);//判断当前状态和队首状态加上新的价值哪个更大
				while(hh<=tt && g[k]>=g[q[tt]]+(k-q[tt])/w*v) tt--;//判断当前状态是否比队尾的价值更大,更大的话将他们一一排出
				q[++tt]=k;//入队
			}
		}
	}
	cout << f[W];
	return ;
}

混合背包

模板例题

点这里~~

思路

这种题目看起来很吓人,可是只要领悟了前面几种背包的中心思想,并将其合并在一起就可以了
我们可以将他们分为两类:

  • 完全背包
  • 多重背包(01背包相当于k为1)

那么我们只需要分别处理上面两种类型即可
遇上完全背包就套完全背包的模板,遇上01背包和多重背包就套多重背包的模板
(注意:多重背包的朴素算法会超时,因此我们在这里考虑用二进制优化和队列优化的方法来做
接下来看代码:

code1

int f[N];
void solve(){
	int n,W;
	cin >> n >> W;
	for(int i=1;i<=n;++i){
		int w,v,s;
		cin >> w >> v >> s;
		if(s==0){//完全背包
		  for(int j=w;j<=W;++j){
		  	f[j]=max(f[j],f[j-w]+v);
		  }
		}
		else{
			if(s==-1) s=1;//01背包转换多重背包
			for(int k=1;k<=s;k<<=1){//二进制优化
				for(int j=W;j>=k*w;--j){
					f[j]=max(f[j],f[j-k*w]+k*v);//直接进行循环,比较大小
				}
				s-=k;//减去个数
			}
			if(s){
				for(int j=W;j>=s*w;--j){
					f[j]=max(f[j],f[j-s*w]+s*v);
				}
			}
		}
	}
	cout << f[W];
	return ;
}

如果套队列优化的多重背包的模板,实际上不需要分完全背包和多重背包
我们只需要将背包个数s设置为无穷大,这样每次都不会将队首出队,相当于无限放入物品(即完全背包)

code2

int q[N],f[N],g[N];
void solve(){
	int n,W;
    cin >> n >> W;
    for(int i=1;i<=n;++i){
    	memcpy(g,f,sizeof f);
    	int w,v,s;
    	cin >> w >> v >> s;
    	if(s==-1) s=1;
    	if(s==0) s=1e6;
    	for(int j=0;j<w;++j){
    		int hh=0,tt=-1;
    		for(int k=j;k<=W;k+=w){
    			if(hh<=tt && q[hh]<k-s*w) hh++;
    			if(hh<=tt) f[k]=max(g[k],g[q[hh]]+(k-q[hh])/w*v);
    			while(hh<=tt && g[k]>=g[q[tt]]+(k-q[tt])/w*v) tt--;
    			q[++tt]=k;
			}
		}
	}
	cout << f[W];
	return ;
}

二维费用背包

模板例题

点这里~~

思路

这种题型很明显是 0-1 背包问题的变形,可是不同的是选一个物品会消耗两种价值(容量、重量),只需在状态中增加一维存放第二种价值即可
注意:开三维存放物品编号就不合适了,因为容易 MLE,我们可以像01背包一样,将三维压缩成二维

code

int f[105][105];
void solve(){
	int n,W,M;
	cin >> n >> W >> M;
	for(int i=1;i<=n;++i){
		int w,m,v;
		cin >> w >> m >> v;
		for(int j=W;j>=w;--j)//和01背包一样,倒着遍历
		   for(int k=M;k>=m;--k){
		   	f[j][k]=max(f[j][k],f[j-w][k-m]+v);//相当于滚动数组,每次都在原来的状态进行新状态的转移
		   }
	}
	cout << f[W][M];
	return ;
}

标签:多重,背包,int,cin,进阶篇,DP,数组,模板
From: https://blog.csdn.net/wh233z/article/details/140963492

相关文章

  • 怎么一键自动备份WordPress博客
    插件名称:BackWPup●第1步插件安装完毕启动后,点击【BackWPup】→【AddNew】,进入后请设定排程名称,接着请勾选要备份的类型(在此只选择DatabaseBackup资料库备份),接着请选择资料表(预设全选),再来请选择备份送达地点(在此选「BackuptoE-Mail」),之后请设定排程时间,然后点﹝SaveChanges﹞......
  • WordPress 简单吗?
     是的,WordPress对于初学者来说非常简单易用。WordPress是一个开源的网站构建平台,专为简单性和用户友好性而设计,使其非常适合初学者和技术水平较低的用户。以下是几个使WordPress易于使用的原因:1.直观的用户界面:WordPress拥有一个干净且直观的界面,菜单和选项清晰易懂。......
  • wordpress教程栏目给大家介绍自定义wordpress文件上传路径的方法
    自WordPress3.5版本开始,隐藏了后台媒体设置页面的“默认上传路径和文件的完整URL地址”选项,可以通过下面的代码将该选项调出来。将下面的代码添加到当前主题functions.php文件中,就可以调出该选项:if(get_option(&#39;upload_path&#39;)==&#39;wp-content/uploads&#39;||get_op......
  • 算法随笔——高级树形DP
    换根DP学习博客https://www.luogu.com.cn/article/wdk0q56f树上背包https://www.luogu.com.cn/problem/P1273简单题意叶子节点有权值,每条边有花费,问最多连接多少个叶子节点到根,使得权值总和大于花费总和。做法设\(dp_{i,x,j}\)为在第x个节点,使用前i个子节点的子树,选j......
  • CF1993D-二分+dp处理中位数
    CF1993D-二分+dp处理中位数大致题意给定两个正整数n和k以及另一个由n个整数组成的数组a。在一次操作中,可以选择a的任意一个大小为k的子数组,然后将其从数组中删除,而不改变其他元素的顺序。更正式地说,假设$(l,r)$是对子数组\(a_l,a_{l+1},…,a_r\)的操作,使得\(r......
  • 动态规划之——背包DP(入门篇)
    文章目录概要说明01背包模板例题题意概要思路code1code201背包的应用题题目来源思路code完全背包模板例题题意概要思路code概要说明本文只讲了01背包和完全背包,至于其他背包问题后续补充01背包模板例题点击这里题意概要思路01背包的模板题首先对于背包问......
  • 换根 dp
    板子P2986[USACO10MAR]GreatCowGatheringGP3478[POI2008]STA-StationP3047[USACO12FEB]NearbyCowsG很好的一题f[i][j]表示与点i(向下(点i儿子1中的点))距离为j的点的权值和g[i][j]表示与点i(所有)距离为j的点的权值和需要特别注意的是刚开始时g[i][j]=......
  • 状态压缩DP
    状态压缩DP定义:‌状态压缩是一种使用二进制数来表示状态的方法,通常用于表示只有两种状态(0和1)的对象。Acwing,291蒙特里安的梦想291.蒙德里安的梦想-AcWing题库题目概览求把N×......
  • 常见CMS漏洞(WordPress、DeDeCMS、ASPCMS、PHPMyadmin、Pageadmin)
    目录一:WordPress步骤一:进入Vulhub靶场并执行以下命令开启靶场;在浏览器中访问并安装好子...步骤二:思路是修改其WP的模板写入一句话木马后门并访问其文件即可GetShel;登陆WP后点击【外观】--》【编辑】--》404.php步骤三:访问以下连接即可获取WebShel...姿势二:上传主......
  • Luogu P1777 帮助 题解 [ 紫 ] [ 线性 dp ] [ 状压 dp ]
    帮助:大毒瘤!!!调了我2h,拍了我2h,最后没调出来,重写才AC。wdnmd。思路这题主要是线性dp,而状压dp只是最后在统计答案时的一个辅助。首先定义\(dp[i][j][k]\)为考虑前\(i\)本书,已经抽出来了\(j\)本,最后没被抽出来的一本书是高度\(k\)的最小混乱度。每次放入的书与最后一位......