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