先设 表示最小的 .
当 使用了 次时, 可以用 次 替代,
举个例子, 假如当前选物品的情况是 , 此时只要在 中再选出任意一个物品 ,
则 个 就可以被 个 顶替 .
由此可以想到一个比暴力更优的背包方法,
使用总容量 的背包进行 , 然后对于每个 的判断, 只需将 模上 (等同于全部被 顶替了), 此时 一定是在背包容量内的, 直接判断即可 .
这样可以有 .
使用 为上界的原因是 是 不能消任何数字的极限情况, 以 为上界就可以顾及到所有的装填方法 .
思考能否以更小的背包容量解决这个问题,
设 , 且 , 则 ,
即对于 相同的, 要凑成 , 可以先凑成 , 然后用 在 的基础上填充 凑成 ,
换句话说, 若两个数模 的值相同, 且除 商 较小的那个数字可以凑出, 则较大的那个数字也可以凑出 .
上面这句话是 下方法的思想 核心 .
由此可以设 表示 所有满足 且能被凑出的 中, 最小的 ,
用类似最短路的算法转移, 最后只需检查当前数字是否满足条件 即可 .
时间复杂度 .
#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
", Ans);
return 0;
}