[HAOI2018]奇怪的背包

Description

\(n\) 种权值各不相同的物品,每种无穷个,给定数 \(P\)\(q\) 次询问,每次问有多少种选物品的方案(每种任意个),使得在模 \(P\) 意义下同余于 \(w\)。方案不同当且仅当物品种类不同。

Solution

容易知道(考虑裴蜀定理),对于一种价值为 \(v\) 的物品,使用无穷次后,得到的值只会是

\[0,\gcd(v,P),2\gcd(v,P),3\gcd(v,P),\dots \]

所以考虑直接将 \(v\) 变为 \(\gcd(P,v)\)。进一步(多个数的裴蜀定理),对于多种物品,能凑出来的值有

\[0,\gcd(v_1,v_2,\dots),2\gcd(v_1,v_2,\dots),3\gcd(v_1,v_2,\dots),\dots \]

所以方案合法当且仅当 \(\gcd(v_1,v_2,\dots)|w\)
容易发现 \(\gcd\) 只会取 \(P\) 的约数,所以考虑算每种约数有多少种方案可以凑出。用 \(dp[i][j]\) 表示考虑了前 \(i\) 个约数,\(\gcd\) 是第 \(j\) 个约数的方案。转移就很显然。
算答案只需要枚举 \(w\) 的约数即可。但这样还是不够优秀。容易发现最终的答案其实是 \(w\)\(P\) 的高维前缀的交集。这个交集部分其实就是 \(\gcd(w,P)\),而这一定是 \(P\) 的约数。所以可以考虑预处理高维前缀和。

int main(){
    int n=read(),Q=read(),P=read(); pw[0]=1;
    for(int i=1;i*i<=P;i++)
        if(P%i==0){ p[++cnt]=i; if(i*i!=P) p[++cnt]=P/i;}
    sort(p+1,p+1+cnt); for(int i=1;i<=n;i++) pw[i]=pw[i-1]*2ll%Mod;
    for(int i=1;i<=n;i++)
        a[i]=gcd(read(),P),c[Get(a[i])]++;
    for(int i=1;i<=cnt;i++){
        for(int k=1;k<=cnt;k++){
            int &x=dp[Get(gcd(p[i],p[k]))];
            x=(x+1ll*dp[k]*(pw[c[i]]-1)%Mod)%Mod;
        }
        dp[i]=(dp[i]+pw[c[i]]-1)%Mod;
    }
    for(int i=cnt;i;i--)
        for(int j=1;j<i;j++)
            if(p[i]%p[j]==0) dp[i]=(dp[i]+dp[j])%Mod;
    while(Q--) printf("%d\n",dp[Get(gcd(P,read()))]);
}
posted @ 2021-09-07 08:23  Kreap  阅读(23)  评论(0编辑  收藏  举报