Problem 多重背包问题 III
有
第
输出最大价值。
Solution
容易想到暴力多重背包的做法
#include<bits/stdc++.h>
using namespace std;
const int N=1003,M=20009;
int n,V,w[N],v[N],cnt[N],dp[N][M];
int main(){
scanf("%d%d",&n,&V);
for(int i=1;i<=n;i++)scanf("%d%d%d",&w[i],&v[i],&cnt[i]);
for(int i=1;i<=n;i++)for(int j=0;j<=V;j++)for(int k=0;k*w[i]<=j&&k<=cnt[i];k++)
dp[i][j]=max(dp[i][j],dp[i-1][j-w[i]*k]+v[i]*k);
printf("%d\n",dp[n][V]);
return 0;
}
这个不多解释了
但
发现第一维用处不大可以压掉
将原转移式 dp[i][j]=max(dp[i][j],dp[i-1][j-w[i]*k]+v[i]*k);
变成 dp[j]=max(dp[j],dp[j-w[i]*k]+v[i]*k);
但为了使后面一维存储上一行的信息
我们需要让j
倒序枚举
即如下代码
#include<bits/stdc++.h>
using namespace std;
const int N=1003,M=20009;
int n,V,w[N],v[N],cnt[N],dp[M];
int main(){
scanf("%d%d",&n,&V);
for(int i=1;i<=n;i++)scanf("%d%d%d",&w[i],&v[i],&cnt[i]);
for(int i=1;i<=n;i++)for(int j=V;j>=0;j--)for(int k=0;k*w[i]<=j&&k<=cnt[i];k++)
dp[j]=max(dp[j],dp[j-w[i]*k]+v[i]*k);
printf("%d\n",dp[V]);
return 0;
}
但这个代码时间复杂度高达
是无法通过的
思考如何优化
我们发现问题关键在于如何快速求出
发现一个事情:
这话说得,跟说了话似的
而显然
所以我们枚举每一个余数
然后枚举这个余数下每一个合法的值
for(int i=1;i<=n;i++){
for(int j=0;j<w[i];j++){
for(int k=0;k*w[i]+j<=V;k++){
//QwQ
}
}
}
声明一下:这是一个
因为
下一步我们发现同一余数的 DP 值只会用到前 cnt[i] 个
想求
这个式子中减去的
这其实就是一个类似滑动窗口的玩应
可以搞一个单调队列上去
但是有个问题:后半部分咋搞?
其实我们可以往单调队列里插一个
这样元素之间的大小关系没变
用的时候只需要 dp[j+k*w[i]]=dp[j+W[i]*q.front()]+v[i]*(k-q.front())
这也恰恰是只记下标不记录值的优点
如果你手写
如果你还是没理解,看一下代码
空间暴力代码
#include<bits/stdc++.h>
using namespace std;
const int N=1003,M=20009;
int n,V,w[N],v[N],cnt[N],dp[N][M];
class Deque{
public:
bool empty(){return tt<ff;}
int front(){return k[ff];}
int back(){return k[tt];}
void pop_front(){++ff;}
void pop_back(){--tt;}
void push_back(int d){k[++tt]=d;}
void clear(){ff=1,tt=0;}
void show(){for(int i=ff;i<=tt;i++)printf("%d ",k[i]);puts("<");}
private:
int k[M],ff=1,tt=0;
}q;
int main(){
scanf("%d%d",&n,&V);
for(int i=1;i<=n;i++)scanf("%d%d%d",&w[i],&v[i],&cnt[i]);
for(int i=1;i<=n;i++){
for(int j=0;j<w[i];j++){
q.clear();
for(int k=0;k*w[i]+j<=V;k++){
while(!q.empty()&&k-q.front()>cnt[i])q.pop_front();
while(!q.empty()&&dp[i-1][q.back()*w[i]+j]-v[i]*q.back()<=dp[i-1][k*w[i]+j]-v[i]*k)q.pop_back();
q.push_back(k);
dp[i][j+k*w[i]]=dp[i-1][j+q.front()*w[i]]+v[i]*(k-q.front());
}
}
for(int j=1;j<=V;j++)dp[i][j]=max(dp[i][j],dp[i][j-1]);
}
printf("%d\n",dp[n][V]);
return 0;
}
空间优化代码
#include<bits/stdc++.h>
using namespace std;
const int N=1003,M=20009;
int n,V,w[N],v[N],cnt[N],dp[M],pre[M];
class Deque{
public:
bool empty(){return tt<ff;}
int front(){return k[ff];}
int back(){return k[tt];}
void pop_front(){++ff;}
void pop_back(){--tt;}
void push_back(int d){k[++tt]=d;}
void clear(){ff=1,tt=0;}
void show(){for(int i=ff;i<=tt;i++)printf("%d ",k[i]);puts("<");}
private:
int k[M],ff=1,tt=0;
}q;
int main(){
scanf("%d%d",&n,&V);
for(int i=1;i<=n;i++)scanf("%d%d%d",&w[i],&v[i],&cnt[i]);
for(int i=1;i<=n;i++){
for(int j=1;j<=V;j++)pre[j]=dp[j];
for(int j=0;j<w[i];j++){
q.clear();
for(int k=0;k*w[i]+j<=V;k++){
while(!q.empty()&&k-q.front()>cnt[i])q.pop_front();
while(!q.empty()&&pre[q.back()*w[i]+j]-v[i]*q.back()<=pre[k*w[i]+j]-v[i]*k)q.pop_back();
q.push_back(k);
dp[j+k*w[i]]=pre[j+q.front()*w[i]]+v[i]*(k-q.front());
}
}
for(int j=1;j<=V;j++)dp[j]=max(dp[j],dp[j-1]);
}
printf("%d\n",dp[V]);
return 0;
}
后记:
单调队列背包
因为系统的
交
终于点亮该技能啦(逃)
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战