P r e s e n t Present Present
正 解 部 分 \color{red}{正解部分} 正解部分
先设 p 1 p_1 p1 表示最小的 p p p .
当
p
x
p_x
px 使用了
p
1
p_1
p1 次时, 可以用
p
x
p_x
px 次
p
1
p_1
p1 替代,
举个例子, 假如当前选物品的情况是
∑
i
=
2
N
(
p
1
−
1
)
p
i
\sum\limits_{i=2}^N(p_1-1)p_i
i=2∑N(p1−1)pi, 此时只要在
x
∈
[
2
,
N
]
x∈[2, N]
x∈[2,N] 中再选出任意一个物品
p
x
p_x
px,
则
p
1
p_1
p1个
p
x
p_x
px 就可以被
p
x
p_x
px个
p
1
p_1
p1 顶替 .
由此可以想到一个比暴力更优的背包
d
p
dp
dp方法,
使用总容量
∑
i
=
1
N
p
1
p
i
\sum\limits_{i=1}^Np_1p_i
i=1∑Np1pi 的背包进行
d
p
dp
dp, 然后对于每个
a
i
a_i
ai 的判断, 只需将
a
i
a_i
ai 模上
∑
i
=
1
N
p
1
p
i
\sum\limits_{i=1}^Np_1p_i
i=1∑Np1pi (等同于
∑
i
=
1
N
p
1
p
i
\sum\limits_{i=1}^Np_1p_i
i=1∑Np1pi全部被
p
1
p_1
p1 顶替了), 此时
a
i
a_i
ai 一定是在背包容量内的, 直接判断即可 .
这样可以有 60 p t s 60pts 60pts .
使用 ∑ i = 1 N p 1 p i \sum\limits_{i=1}^Np_1p_i i=1∑Np1pi 为上界的原因是 ∑ i = 1 N ( p 1 − 1 ) p i \sum\limits_{i=1}^N(p_1-1)p_i i=1∑N(p1−1)pi 是 p 1 p_1 p1 不能消任何数字的极限情况, 以 ∑ i = 1 N p 1 p i \sum\limits_{i=1}^Np_1p_i i=1∑Np1pi 为上界就可以顾及到所有的装填方法 .
思考能否以更小的背包容量解决这个问题,
设
x
=
y
m
o
d
p
1
x=y \mod p_1
x=ymodp1, 且
x
<
y
x < y
x<y, 则
y
−
x
=
0
m
o
d
p
1
y - x = 0 \mod p_1
y−x=0modp1,
即对于
m
o
d
p
1
mod\ p_1
mod p1 相同的
x
,
y
x, y
x,y, 要凑成
y
y
y, 可以先凑成
x
x
x, 然后用
p
1
p_1
p1 在
x
x
x 的基础上填充 凑成
y
y
y,
换句话说, 若两个数模
p
1
p_1
p1 的值相同, 且除
p
1
p_1
p1 商 较小的那个数字可以凑出, 则较大的那个数字也可以凑出 .
上面这句话是 下方法的思想 核心 .
由此可以设
F
[
x
]
F[x]
F[x] 表示 所有满足
i
%
p
1
=
x
i\%p_1 = x
i%p1=x 且能被凑出的
i
i
i 中, 最小的
⌊
i
p
1
⌋
\lfloor \frac{i}{p_1} \rfloor
⌊p1i⌋,
用类似最短路的算法转移, 最后只需检查当前数字
a
i
a_i
ai是否满足条件
F
[
a
i
%
p
1
]
≤
⌊
a
i
p
1
⌋
F[a_i \% p_1] \le \lfloor \frac{a_i}{p_1} \rfloor
F[ai%p1]≤⌊p1ai⌋ 即可 .
时间复杂度 O ( p 1 l o g p 1 ) O(p_1logp_1) O(p1logp1) .
实 现 部 分 \color{red}{实现部分} 实现部分
#include<bits/stdc++.h>
#define reg register
#define fi first
#define se second
typedef std::pair<int, int> pr;
int read(){
char c;
int s = 0, flag = 1;
while((c=getchar()) && !isdigit(c))
if(c == '-'){ flag = -1, c = getchar(); break ; }
while(isdigit(c)) s = s*10 + c-'0', c = getchar();
return s * flag;
}
int N;
int M;
int Ans;
int p[505];
int a[300005];
int F[200005];
int main(){
freopen("present.in", "r", stdin);
freopen("present.out", "w", stdout);
N = read(), M = read();
for(reg int i = 1; i <= N; i ++) p[i] = read();
for(reg int i = 1; i <= M; i ++) a[i] = read();
memset(F, 0x3f, sizeof F);
F[0] = 0;
std::priority_queue <pr, std::vector<pr>, std::greater<pr> > Q;
Q.push(pr(0, 0));
while(!Q.empty()){
int ft = Q.top().se; Q.pop();
for(reg int i = 2; i <= N; i ++){
int to = ft + p[i], cur = to%p[1];
if(F[cur] > F[ft] + to/p[1]){
F[cur] = F[ft] + to/p[1];
Q.push(pr(F[cur], cur));
}
}
}
for(reg int i = 1; i <= M; i ++)
if(F[a[i]%p[1]] <= a[i]/p[1]) Ans ++;
printf("%d\n", Ans);
return 0;
}