LibreOJ #2523.「HAOI2018」奇怪的背包 动态规划+数论

题意

C C 非常擅长背包问题,他有一个奇怪的背包,这个背包有一个参数P,当他向这个背包内放入若干个物品后,背包的重量是物品总体积对 P P 取模后的结果.现在小C n n 种体积不同的物品,第i种占用体积为 Vi V i ,每种物品都有无限个.他会进行 q q 次询问,每次询问给出重量wi,你需要回答有多少种放入物品的方案,能将一个初始为空的背包的重量变为 wi w i .注意,两种方案被认为是不同的,当且仅当放入物品的种类不同,而与每种物品放入的个数无关.不难发现总的方案数为 2n 2 n 。由于答案可能很大,你只需要输出答案对 109+7 10 9 + 7 取模的结果.
n,q106,P,Vi,wi109 n , q ≤ 10 6 , P , V i , w i ≤ 10 9

分析

分析一波不难发现我们要求的就是有多少个集合满足 gcd(Vi,P)|wi g c d ( V i , P ) | w i
先把所有的 Vi V i 变成 gcd(Vi,P) g c d ( V i , P ) ,所有的 wi w i 变成 gcd(wi,P) g c d ( w i , P ) ,然后把所有 P P 的约数找出来。
显然P的约数个数是 O(103) O ( 10 3 ) 级别的。
那么我们对所有数进行dp,设 f[i,j] f [ i , j ] 表示前i个数中选出若干个,满足它们的 gcd g c d 恰好为 j j 的方案,然后就可以O(1)询问了。
时间复杂度 O(σ2(P)) O ( σ 2 ( P ) )

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>

typedef long long LL;

const int N=2005;
const int MOD=1000000007;

int n,m,p,tot,w[N],id1[100005],id2[100005],bin[1000005],s[N],f[N][N],B,ans[N];

int gcd(int x,int y)
{
    if (!y) return x;
    else return gcd(y,x%y);
}

void pre()
{
    B=sqrt(p);
    for (int i=1;i*i<=p;i++)
        if (p%i==0)
        {
            w[++tot]=i;id1[i]=tot;
            if (p/i!=i) w[++tot]=p/i,id2[i]=tot;
        }
}

int main()
{
    scanf("%d%d%d",&n,&m,&p);
    pre();
    for (int i=1;i<=n;i++)
    {
        int x;scanf("%d",&x);
        x=gcd(x,p);
        if (x<=B) s[id1[x]]++;
        else s[id2[p/x]]++;
    }
    bin[0]=1;
    for (int i=1;i<=n;i++) bin[i]=bin[i-1]*2%MOD;
    f[0][id2[1]]=1;
    for (int i=0;i<tot;i++)
        for (int j=1;j<=tot;j++)
        {
            if (!f[i][j]) continue;
            (f[i+1][j]+=f[i][j])%=MOD;
            int x=gcd(w[i+1],w[j]),k=x<=B?id1[x]:id2[p/x];
            (f[i+1][k]+=(LL)f[i][j]*(bin[s[i+1]]-1)%MOD)%=MOD;
        }
    for (int i=1;i<=tot;i++)
        for (int j=1;j<=tot;j++)
            if (w[i]%w[j]==0) (ans[i]+=f[tot][j])%=MOD;
    while (m--)
    {
        int x;scanf("%d",&x);
        x=gcd(x,p);
        printf("%d\n",x<=B?ans[id1[x]]:ans[id2[p/x]]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值