三种方法。
写得比较优秀的二进制拆分+分数据范围处理、多重背包可行性、单调队列优化。
第一种方法。
一般的二进制拆分复杂度是
Θ
(
m
∑
l
o
g
2
C
i
)
\mathcal{\Theta(m\sum log_2C_i)}
Θ(m∑log2Ci)。算一下就能发现这个上界卡到
1
e
8
\mathcal{1e8}
1e8。
考虑怎么降低处理数量多的硬币的复杂度。显然假如
C
i
×
V
i
≥
m
\mathcal{C_i×V_i\ge m}
Ci×Vi≥m就可以当作完全背包来跑。
那就这么做。
注意多重背包的二进制拆分是允许重复的,
如果是单纯把 C i \mathcal{C_i} Ci二进制表示里为 1 \mathcal{1} 1的位拿出来并不能组合得到所有不大于 C i \mathcal{C_i} Ci的方案
一定要从 1 \mathcal{1} 1开始倍增,最后把减剩下的 C i \mathcal{C_i} Ci单独拿出来再跑一次。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<cstring>
using namespace std;
int n, m, c;
int a[105];
bool f[100005];
int main() {
while (~scanf("%d%d", &n, &m)) {
if (!n && !m) return 0;
for (int i = 1; i <= m; ++i) {
f[i] = 0;
}
f[0] = 1;
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
for (int i = 1; i <= n; ++i) {
scanf("%d", &c);
if (c * a[i] < m) {
for (int t, k = 1; k <= c; k <<= 1) {
t = k * a[i];
for (int j = m; j >= t; --j) {
f[j] |= f[j - t];
}
c -= k;
if (!c) break;
}
if (c) {
int t = c * a[i];
for (int j = m; j >= t; --j) {
f[j] |= f[j - t];
}
}
}
else {
for (int j = a[i]; j <= m; ++j) {
f[j] |= f[j - a[i]];
}
}
}
int ans = 0;
for (int i = 1; i <= m; ++i) ans += f[i];
printf("%d\n", ans);
}
return 0;
}
第二种方法。
实际上这种方法是很自然的。
我的代码比较丑,漂亮一点的代码应该看一眼就可以明白原理了。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<cstring>
using namespace std;
int n, m, c, ans;
int a[105];
int vis[100005];
bool f[100005];
void Check(const int &x) {
for (int i = 1; i <= m; ++i) {
vis[i] = 0;
}
f[0] = 1;
for (int i = a[x]; i <= m; ++i) {
if (vis[i - a[x]] == c) continue;
if (f[i] || !f[i - a[x]]) continue;
f[i] = 1;
vis[i] = vis[i - a[x]] + 1;
++ans;
}
}
int main() {
while (~scanf("%d%d", &n, &m)) {
if (!n && !m) return 0;
ans = 0;
memset(f, 0, sizeof(f));
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
for (int i = 1; i <= n; ++i) {
scanf("%d", &c);
Check(i);
}
printf("%d\n", ans);
}
return 0;
}
第三种方法。
实际上这道题目完全没必要用单队解、不过也是可以的。
多重背包朴素式子
f
j
=
m
a
x
{
f
j
−
k
c
o
s
t
i
+
k
v
a
l
u
e
i
  
∣
  
k
∈
[
0
,
c
o
u
n
t
i
]
}
\mathcal{f_j=max\{f_{j-kcost_i}+kvalue_i\;|\;k\in[0,count_i]\}}
fj=max{fj−kcosti+kvaluei∣k∈[0,counti]}
外层枚举物品,把
j
\mathcal{j}
j重标号,按照
%
c
o
s
t
i
\mathcal{\%cost_i}
%costi的余数分类,转化式子。可以得到
f
j
′
=
m
a
x
{
f
k
′
−
k
′
v
a
l
u
e
  
∣
  
k
′
∈
[
j
′
−
c
o
u
n
t
i
,
j
′
]
}
+
j
′
v
a
l
u
e
,
  
j
′
c
o
s
t
i
+
R
e
m
a
i
n
d
e
r
=
j
\mathcal{f_{j'}=max\{f_{k'}-k'value\;|\;k'\in[j'-count_i,j']\}+j'value,\;j'cost_i+Remainder=j}
fj′=max{fk′−k′value∣k′∈[j′−counti,j′]}+j′value,j′costi+Remainder=j(枚举
R
e
m
a
i
n
d
e
r
\mathcal{Remainder}
Remainder)
这是单调队列优化的普遍形式,在单调队列中维护
m
a
x
{
f
k
′
−
k
′
v
a
l
u
e
}
,
  
k
∈
[
j
−
c
o
u
n
t
i
,
j
]
\mathcal{max\{f_{k'}-k'value\},\;k\in[j-count_i,j]}
max{fk′−k′value},k∈[j−counti,j]
每次将
f
k
′
−
k
′
v
a
l
u
e
\mathcal{f_{k'}-k'value}
fk′−k′value入队,得到的最大值是
∗
q
u
e
u
e
.
f
r
o
n
t
(
)
+
j
v
a
l
u
e
\mathcal{*queue.front()+jvalue}
∗queue.front()+jvalue
到这道题目里面有
c
o
s
t
i
=
v
a
l
u
e
i
\mathcal{cost_i=value_i}
costi=valuei,于是每个背包只有能否被覆盖的状态
于是单调队列维护的就是有没有
1
\mathcal{1}
1。
那么只要知道前面这一段有没有
1
\mathcal{1}
1就可以了。