Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 48043 Accepted Submission(s): 20489
Problem Description
急!灾区的食物依然短缺!
为了挽救灾区同胞的生命,心系灾区同胞的你准备自己采购一些粮食支援灾区,现在假设你一共有资金n元,而市场有m种大米,每种大米都是袋装产品,其价格不等,并且只能整袋购买。
请问:你用有限的资金最多能采购多少公斤粮食呢?
Input
输入数据首先包含一个正整数
C
C
C,表示有
C
C
C组测试用例,每组测试用例的第一行是两个整数
n
n
n和
m
(
1
<
=
n
<
=
100
,
1
<
=
m
<
=
100
)
m(1<=n<=100, 1<=m<=100)
m(1<=n<=100,1<=m<=100),分别表示经费的金额和大米的种类,然后是m行数据,每行包含3个数p,h和
c
(
1
<
=
p
<
=
20
,
1
<
=
h
<
=
200
,
1
<
=
c
<
=
20
)
c(1<=p<=20,1<=h<=200,1<=c<=20)
c(1<=p<=20,1<=h<=200,1<=c<=20),分别表示每袋的价格、每袋的重量以及对应种类大米的袋数。
Output
对于每组测试数据,请输出能够购买大米的最多重量,你可以假设经费买不光所有的大米,并且经费你可以不用完。每个实例的输出占一行。
Sample Input
1
8 2
2 100 4
4 100 2
Sample Output
400
解题思路
分析:其实这就是多重背包,但会发现用最赤裸裸的方法是过不了的,连二进制优化也都不行。那下面就来介绍一下用单调队列来实现的时间复杂度为
O
(
n
m
)
O(nm)
O(nm)的多重背包。
很显然前两个循环是少不了的,那么我们来想如何去掉第三个循环,从而使时间复杂度变为我们想要的 O ( n m ) O(nm) O(nm)。
若
w
[
i
]
w[i]
w[i]表示物品重量,
v
[
i
]
v[i]
v[i]表示价值,
c
[
i
]
c[i]
c[i]表示数量,我们知道朴素状态转移方程:
令
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
]
[
j
]
,
f
[
i
−
1
]
[
j
−
k
∗
w
[
i
]
]
+
k
∗
v
[
i
]
)
f[i][j] = max(f[i][j],f[i-1][j-k*w[i]] + k*v[i])
f[i][j]=max(f[i][j],f[i−1][j−k∗w[i]]+k∗v[i])
(
1
<
=
k
<
=
c
[
i
]
)
(1 <= k <= c[i])
(1<=k<=c[i]) 这里的
k
k
k 是指取第
i
i
i 种物品
k
k
k 件。
如果令
a
=
j
/
w
[
i
]
a = j / w[i]
a=j/w[i] (a为最多能选多少个i物品), b = j %w[i] 那么
j
=
a
∗
w
[
i
]
+
b
j = a * w[i] + b
j=a∗w[i]+b.
这里用
k
k
k 表示的意义改变,
k
k
k 表示取第
i
i
i 种物品的件数比
a
a
a 少几件。
现在我们枚举
b
b
b,也就是余数,我们按余数来分层,不同层之间不能相互转移。
那么
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
]
[
j
]
,
f
[
i
−
1
]
[
b
+
k
∗
w
[
i
]
]
−
k
∗
v
[
i
]
)
+
a
∗
v
[
i
]
)
f[i][j] = max(f[i][j] ,f[i-1][b+k*w[i]] - k*v[i]) + a*v[i])
f[i][j]=max(f[i][j],f[i−1][b+k∗w[i]]−k∗v[i])+a∗v[i])
可以发现,
f
[
i
−
1
]
[
b
+
k
∗
w
[
i
]
]
−
k
∗
v
[
i
]
f[i-1][b+k*w[i]] - k*v[i]
f[i−1][b+k∗w[i]]−k∗v[i] 只与
k
k
k 有关,而这个
k
k
k是一段连续的。我们要做的就是求出
f
[
i
−
1
]
[
b
+
k
∗
w
[
i
]
]
−
k
∗
v
[
i
]
f[i-1][b+k*w[i]] - k*v[i]
f[i−1][b+k∗w[i]]−k∗v[i] 在
k
k
k 取可行区间内时的最大值。
代码
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
int t,n,m,w,v,s,h,T,c,f[10010],q[10010],num[10010];
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d",&m,&n);
for(int i=1;i<=n;i++)
{
scanf("%d%d%d",&w,&v,&s);
s=min(s,m/w);
for(int d=0;d<w;d++)//为前文的b,也就是余数
{
h=1,t=1;
for(int j=0;j<=(m-d)/w;j++)//为前文的k
{
int y=f[j*w+d]-v*j;
while(h<t&&q[t-1]<=y)--t;
q[t]=y,num[t++]=j;
while(h<t&&j-num[h]>s)++h;
f[j*w+d]=max(f[j*w+d],q[h]+v*j);
}
}
}
printf("%d\n",f[m]);
memset(f,0,sizeof(f));
memset(q,0,sizeof(q));
memset(num,0,sizeof(num));
}
}