P r e s e n t Present Present


<mstyle mathcolor="red"> </mstyle> \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=2N(p11)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=1Np1pi 的背包进行 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=1Np1pi (等同于 i = 1 N p 1 p i \sum\limits_{i=1}^Np_1p_i i=1Np1pi全部被 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=1Np1pi 为上界的原因是 i = 1 N ( p 1 1 ) p i \sum\limits_{i=1}^N(p_1-1)p_i i=1N(p11)pi p 1 p_1 p1 不能消任何数字的极限情况, 以 i = 1 N p 1 p i \sum\limits_{i=1}^Np_1p_i i=1Np1pi 为上界就可以顾及到所有的装填方法 .


思考能否以更小的背包容量解决这个问题,

x = y m o d &ThinSpace;&ThinSpace; p 1 x=y \mod p_1 x=ymodp1, 且 x &lt; y x &lt; y x<y, 则 y x = 0 m o d &ThinSpace;&ThinSpace; p 1 y - x = 0 \mod p_1 yx=0modp1,
即对于 m o d <mtext>   </mtext> 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) .


<mstyle mathcolor="red"> </mstyle> \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;
}