About 单调队列优化多重背包
20230921
About 单调队列优化多重背包
前言
之前打了给代码,隐隐约约知道了意思。
但不完全明白~
于是经过自己的钻研,终于理解。
模板题(P1776 宝物筛选)
Statement
01 背包中每个数只能选一次改成可以选
Solution
直接 dp 可以做到
很显然,三次分别枚举哪一个物品,背包质量是多少和选的个数即可。
在此基础上,还可以进行二进制优化,但没有什么思维含量,这里不赘述。
怎么就和单调队列扯上关系了?
考虑先列出
设
于是
同理我们可以得到
以此类推一直就会推到
很容易发现其实这里的
而我们又是想找到这中间的最大值——
不难想到用单调队列维护。
对于每一个同余的组,我们都考虑去枚举
从而在每一次单调队列中进行更新,
整个时间复杂度是
看代码会更容易理解:
/*单调队列优化*/
#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
Solution
我们考虑没有被放进去的最小体积的那个物品,
于是这个数对答案的贡献就是
所以我们考虑从大到小枚举每一个物品,
对其进行多重背包,其实就每一次加入一个物品。
发现在对于最小的没选的物品
于是对于每次枚举我们直接用单调队列加入就可以了。
时间复杂度
考虑这道题是求方案数,于是实际上只用维护
#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
单调队列优化多重背包基于对余数的讨论,再用单调队列维护最大值
速度很快,能做到
本文作者:H_W_Y
本文链接:https://www.cnblogs.com/H-W-Y/p/17719097.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步