Present [神奇 背包->最短路]

PresentPresent


\color{red}{正解部分}

先设 p1p_1 表示最小的 pp .

pxp_x 使用了 p1p_1 次时, 可以用 pxp_xp1p_1 替代,
举个例子, 假如当前选物品的情况是 i=2N(p11)pi\sum\limits_{i=2}^N(p_1-1)p_i, 此时只要在 x[2,N]x∈[2, N] 中再选出任意一个物品 pxp_x,
p1p_1pxp_x 就可以被 pxp_xp1p_1 顶替 .

由此可以想到一个比暴力更优的背包dpdp方法,
使用总容量 i=1Np1pi\sum\limits_{i=1}^Np_1p_i 的背包进行 dpdp, 然后对于每个 aia_i 的判断, 只需将 aia_i 模上 i=1Np1pi\sum\limits_{i=1}^Np_1p_i (等同于i=1Np1pi\sum\limits_{i=1}^Np_1p_i全部被 p1p_1 顶替了), 此时 aia_i 一定是在背包容量内的, 直接判断即可 .

这样可以有 60pts60pts .

使用 i=1Np1pi\sum\limits_{i=1}^Np_1p_i 为上界的原因是 i=1N(p11)pi\sum\limits_{i=1}^N(p_1-1)p_ip1p_1 不能消任何数字的极限情况, 以 i=1Np1pi\sum\limits_{i=1}^Np_1p_i 为上界就可以顾及到所有的装填方法 .


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

x=ymod  p1x=y \mod p_1, 且 x<yx < y, 则 yx=0mod  p1y - x = 0 \mod p_1,
即对于 mod p1mod\ p_1 相同的x,yx, y, 要凑成 yy, 可以先凑成 xx, 然后用 p1p_1xx 的基础上填充 凑成 yy,
换句话说, 若两个数模 p1p_1 的值相同, 且除 p1p_1 较小的那个数字可以凑出, 则较大的那个数字也可以凑出 .
上面这句话是 下方法的思想 核心 .

由此可以设 F[x]F[x] 表示 所有满足 i%p1=xi\%p_1 = x 且能被凑出的 ii 中, 最小的 ip1\lfloor \frac{i}{p_1} \rfloor,
用类似最短路的算法转移, 最后只需检查当前数字aia_i是否满足条件 F[ai%p1]aip1F[a_i \% p_1] \le \lfloor \frac{a_i}{p_1} \rfloor 即可 .

时间复杂度 O(p1logp1)O(p_1logp_1) .


\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;
}
posted @ 2019-09-01 14:47  XXX_Zbr  阅读(327)  评论(0编辑  收藏  举报