首页 > 其他分享 >About 单调队列优化多重背包

About 单调队列优化多重背包

时间:2023-09-21 09:36:37浏览次数:55  
标签:About 背包 队列 int ch 单调 mod

20230921

About 单调队列优化多重背包

前言

之前打了给代码,隐隐约约知道了意思。

但不完全明白~

于是经过自己的钻研,终于理解。

模板题(P1776 宝物筛选)

Statement

传送门

01 背包中每个数只能选一次改成可以选 \(s_i\) 次。

Solution

直接 dp 可以做到 \(O(n^3)\) ,

很显然,三次分别枚举哪一个物品,背包质量是多少和选的个数即可。

在此基础上,还可以进行二进制优化,但没有什么思维含量,这里不赘述。

怎么就和单调队列扯上关系了?

考虑先列出 \(dp\) 方程式:

设 \(f_{i,j}\) 表示选到第 \(i\) 个物品,容量为 \(j\) 的最大价值, \(w,v\) 分别是重量和价值

于是 \(f_{i,j}=\max(f_{i-1,j},f_{i-1,j-w}+v,f_{i-1,j-2w}+2v,\dots)\),

同理我们可以得到 \(f_{i,j-w}=\max(f_{i-1,j-w},f_{i-1,j-2w}+v,f_{i-1,j-3w}+2v,\dots)\)

以此类推一直就会推到 \(f_{i,r}=f_{i-1,r}\) 这里的 \(r=j \mod w\)。

很容易发现其实这里的 \(j,j-w,j-2w,\dots,r\) 都是 \(\mod w\) 同余的,

而我们又是想找到这中间的最大值——

不难想到用单调队列维护。

对于每一个同余的组,我们都考虑去枚举 \(0w,1w,2w,\dots\) ,

从而在每一次单调队列中进行更新,

整个时间复杂度是 \(O(n^2)\) 的,

看代码会更容易理解:

/*单调队列优化*/ 
#include <bits/stdc++.h>
using namespace std;

const int N=4e4+5;
int n,W,val,w,c,k,d,q[N],h,t,q2[N],dp[N],ans;
//单调队列 q[] 维护下标,q2[] 维护值
int read(){//读入优化
  int x=0,f=1;char ch=getchar();
  while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
  while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
}

int main(){
  /*2023.9.13 H_W_Y P1776 宝物筛选 多重背包*/ 
  n=read();W=read();
  for(int i=1;i<=n;i++){
  	val=read();w=read();c=read();//读入价值,重量和可以选的次数
  	if(w==0){ans+=val*c;continue;}//特判重量为 0 时直接选完
  	k=W/w;//计算把所有体积选满最多能选多少次
  	c=min(c,k);//更新可以选的次数,让 c 变得合理一些(可以优化一点点),避免了无用的枚举
  	for(int d=0;d<w;d++){//枚举 r ,余数
  	  h=t=0;k=(W-d)/w;//清空队列,k 是最多能选的个数
	  for(int j=0;j<=k;j++){//枚举选了多少个
	  	while(h<t&&dp[d+j*w]-j*val>=q2[t-1]) t--;
	  	q[t]=j;q2[t++]=dp[d+j*w]-j*val;
	  	while(h<t&&q[h]<j-c) h++;
	  	dp[d+j*w]=max(dp[d+j*w],q2[h]+j*val);
 	  }	//单调队列里每次把 j*val 减去了方便计算答案,可以意会一下
	}
  }
  printf("%d\n",ans+dp[W]);
  return 0;
}

再来一道例题(P4241 采摘毒瘤)

Statement

传送门

\(n\) 个物品,每种 \(c_i\) 个,占据 \(w_i\) 的空间。背包容量为 \(m\)。现在要求装不下任何一个没装的物品的方案数(对 \(19260817\) 取模)。

\(1 \le n,k_i,d_i \le 500,1 \le m \le 10^5\)

Solution

我们考虑没有被放进去的最小体积的那个物品,

于是这个数对答案的贡献就是 \(\sum_{j=m-sum-w_i+1}^{m-sum} f[i][j]\),

所以我们考虑从大到小枚举每一个物品,

对其进行多重背包,其实就每一次加入一个物品。

发现在对于最小的没选的物品 \(i\) ,比他小的是选完了的。

于是对于每次枚举我们直接用单调队列加入就可以了。

时间复杂度 \(O(nm)\)

考虑这道题是求方案数,于是实际上只用维护 \(f\) 的和而不用求最大值。

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

const int N=505,M=1e5+5,mod=19260817;
int n,m,f[2][M],rev=0,sum=0,ans=0; 
struct node{
  int w,c;//重量和次数
  bool operator <(const node &rhs) const{
    return w>rhs.w;
  } 
}a[N];

int read(){
  int x=0,f=1;char ch=getchar();
  while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
  while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
}

void wrk(int cnt,int w){
  int k,nw=0;
  for(int d=0;d<w;d++){
  	k=(m-d)/w;nw=0;
  	for(int j=0;j<=k;j++){
  	  if(j>cnt) nw=(nw-f[rev^1][d+(j-cnt-1)*w]+mod)%mod;	
  	  f[rev][d+j*w]=nw=(nw+f[rev^1][d+j*w])%mod;
	}
  }
}

int main(){
  /*2023.9.21 H_W_Y P4241 采摘毒瘤 多重背包*/ 
  n=read();m=read();
  for(int i=1;i<=n;i++) a[i].c=read(),a[i].w=read(),sum+=a[i].c*a[i].w;
  if(sum<=m){puts("1");return 0;}
  sort(a+1,a+n+1);
  memset(f,0,sizeof(f));
  f[0][0]=1;
  for(int i=1;i<=n;i++){
  	sum-=a[i].c*a[i].w;rev^=1;
  	wrk(a[i].c-1,a[i].w);
  	for(int j=max(0,m-sum-a[i].w+1);j<=m-sum;j++) ans=(ans+f[rev][j])%mod;
  	wrk(a[i].c,a[i].w);
  }
  printf("%d\n",ans);
  return 0;
}

Conclusion

单调队列优化多重背包基于对余数的讨论,再用单调队列维护最大值

速度很快,能做到 \(O(nW)\)

标签:About,背包,队列,int,ch,单调,mod
From: https://www.cnblogs.com/hwy0622/p/17719097.html

相关文章

  • 单调队列与最大子序和问题
    HDU-1003-MaxSum题意:给定一个长度为$n$的序列$a_1,a_2,a_3,\cdot\cdot\cdota_n(-10^3\lea_i\le10^3,1\len\le10^5)$,找出其中一个和最大的连续子段,并输出最大的和、该子段的起始下标。思路一:DP设$f_i$:以$a_i$结尾的最大子序和。因为......
  • MQ - 01 消息队列发展史&MQ通用架构
    @[toc]导图PreMQ-闲聊MQ一二事儿(Kafka、RocketMQ、Pulsar)MQ发展史基于JMS协议发展出来的ActiveMQ因为功能和稳定性问题,用的人比较少。AMQP是一个消息队列协议规范,它不是一款具体的消息队列。因为不同消息队列的访问协议是不一样的,导致不同的消息队列需要用不同的SDK访......
  • 21_消息队列
    消息队列消息队列1、任务级队列处理函数2、中断级队列处理函数(带中断保护)已经在CMSIS接口中封装但写入生产速度比消费速度快的时候,容易出现数据被覆盖邮箱队列创建、发送、接收、查询、删除传数值osEventevent=osMessageGet(myQueue01Handle,osWaitForever);......
  • Spring Boot + Disruptor 实现消息队列,告诉你什么叫快、什么叫高效!
    01、背景工作中遇到项目使用Disruptor做消息队列,对你没看错,不是Kafka,也不是rabbitmq;Disruptor有个最大的优点就是快,还有一点它是开源的哦,下面做个简单的记录.02、Disruptor介绍Disruptor是英国外汇交易公司LMAX开发的一个高性能队列,研发的初衷是解决内存队列的延迟问题......
  • 消息队列 - RabbitMQ
    RabbitMQ简介RabbitMQ是一个广泛使用的开源消息队列系统,它实现了高级消息队列协议(AMQP)标准,为分布式应用程序提供了强大的消息传递功能。RabbitMQ是Erlang语言编写的,具有高度的可扩展性和可靠性,因此被广泛用于构建分布式、异步的消息通信系统。以下是关于RabbitMQ的详细介......
  • 深入研究消息队列07 架构升级
    36云原生:业界MQ的计算存储分离存算分离架构存算一体架构如下存算分离架构则是目前实现弹性消息队列集群的主要技术方案存算分离架构的优点是计算层为无状态,因此计算层的扩缩容就很方便。缺点是架构变复杂,代码实现难度也提升很多,日常的运维、研发的学习成本也会相应提高。另外计算......
  • ## day13 - 栈与队列part03
    day13-栈与队列part03力扣239.滑动窗口的最大值思路:利用单调队列,很难想的出来。因为每次是进一个数,弹出一个数,因此没必要每次都进行排序,只需要拿到最大值即可。用单调队列实现,是一个双向队列pop()函数:如果要pop的值是队列头部的值,那么就弹出,否则不操作。push()函数:如果......
  • ## day11 - 栈与队列part02
    day11-栈与队列part02力扣20.有效的括号思路:利用栈的特性,遇见左括号就把右括号压栈,遇见右括号,就对比和栈顶元素是否相同,不同就返回false。代码classSolution{public:stack<int>st;boolisValid(strings){if(s.size()%2!=0){......
  • 代码随想录算法训练营-贪心算法-4|406. 根据身高重建队列、452. 用最少数量的箭引爆
    406. 根据身高重建队列1. 一定要想如何确定一个维度,然后再按照另一个维度重新排列。2.先确定身高的维度,降序排列。3. 按照身高排序之后,优先按身高高的people的k来插入,后序插入节点也不会影响前面已经插入的节点,最终按照k的规则完成了队列。4. 局部最优:优先按身高高的peop......
  • 算法训练day11 栈与队列 02 LeetCode20
    算法训练day11栈与队列02LeetCode20.1047.15020.有效的括号:题目:20.有效的括号-力扣(LeetCode)题解:代码随想录(programmercarl.com)classSolution{public:boolisValid(strings){stack<char>str;if(s.size()%2==1)......