题目描述
PS:鸡汤题干
更好的题目阅读体验
分析
这题裸的多重背包会TLE掉。下面就来介绍一下用单调队列来实现的时间复杂度为O(nm)的多重背包。
先来回顾裸的多重背包:
For (int i=1;i<=n;i++)
For (int j=m;j>=0;j--)
For (int k=1;k<=s[i]&&k*w[i]>=j;k++)
F[j]=min(f[j],f[j-k*w[i]]+v[i]*k);
前面两个循环是去不掉的,想想如何去掉第三个循环,达到O(nm)的时间复杂度。
首先我们知道价格为X只能转换到X的倍数的状态,
所以我们**枚举余数(0~m-1)**表示一开始的状态
然后设一个y表示要把这个商品装几个
那么
f
[
x
∗
w
+
k
]
f[x∗w+k]
f[x∗w+k]表示的是当前的最优价值,
又有
f
[
x
∗
w
+
k
]
−
v
∗
x
f[x∗w+k]−v∗x
f[x∗w+k]−v∗x表示未更新的状态
于是得到动态转移方程
f
[
x
∗
w
+
k
]
=
m
a
x
(
q
[
h
]
+
v
∗
x
)
f[x∗w+k]=max(q[h]+v∗x)
f[x∗w+k]=max(q[h]+v∗x)
其他的话就是注意一些细节比如:初始化的数,数组每次清零。
上代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int t,n,m,w,v,s;
int q[1000001],f[1000001],num[1000001];
int main()
{
cin>>t;
for(int i=1;i<=t;i++)
{
cin>>n>>m;
memset(f,0,sizeof(f));//一定要清零(因为多组数据)
for(int j=1;j<=m;j++)
{
cin>>w>>v>>s;//分别表示价格,重量(价值) ,数量
if(s>n/w) s=n/w;
for(int k=0;k<=w-1;k++)//枚举余数!!!余数是0~w-1!!
{
int h=1,t=0;//单调队列这种赋值
for(int x=0;x<=(n-k)/w;x++)//21~34行单调队列维护
{
int tmp=f[x*w+k]-v*x;
while(h<=t&&q[t]<=tmp)
{
t--;
}
t++;
q[t]=tmp;
num[t]=x;
while(h<=t&&x-num[h]>s)
{
h++;
}
f[x*w+k]=max(f[x*w+k],q[h]+v*x);//状态转移方程
}
}
}
cout<<f[n]<<endl;
}
return 0;
}