[HAOI2018]奇怪的背包

[Luogu4495] [BZOJ5302] [LOJ2523]

神仙题的神仙题解

\(F[i][j]\) 表示选到 \(P\) 的第 \(i\) 个约数,选出的数的 \(gcd\) 值为 \(P\) 的第 \(j\) 个约数的方案数
则有 \(F[i][j]=F[i−1][j]+(1+∑_{gcd(a[k],a[i])==a[j]}F[i−1][k])∗(2^{s[i]−1})\)
其中 \(a[i]\) 表示 \(P\) 的第 \(i\) 个约数, \(s[i]\) 表示第 \(i\) 个约数的出现次数
分别对应继承上一轮 , 转移 , 只选这一种的情况

对于每一个询问 \(w_{i}\)我们容易得出答案就是 \(\sum{_{a[i]|w_{i}}F[n][i]}\)
事实上我们可以发现,如果一个数既是 \(P\) 的约数,又是 \(w_{i}\)的约数,那么它一定是 \(gcd(P,w_{i})\)约数,
因此我们只需要统计 \(\sum{_{a[i]|gcd(P,w_{i})}F[n][i]}\)
而这可以在DP之后就预处理出来 : \(G[i]=\sum{_{a[j]|a[i]}F[n][j]}\)

因此我们可以 \(O(1)\) 回答询问
这样搞好像是时间复杂度最优的 , 详见代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cassert>
#include<cmath>
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define Debug(x) cout<<#x<<"="<<x<<endl
using namespace std;
typedef long long LL;
const int INF=1e9+7;
inline LL read(){
    register LL x=0,f=1;register char c=getchar();
    while(c<48||c>57){if(c=='-')f=-1;c=getchar();}
    while(c>=48&&c<=57)x=(x<<3)+(x<<1)+(c&15),c=getchar();
    return f*x;
}

const int MAXN=1e6+5;
const int MAXM=1e4+5;
const int mod=1e9+7;

int v[MAXN],sum[MAXN], num[MAXM],tot[MAXM],f[2][MAXM],g[MAXM];
int n,Q,P,cnt=0,now=0;

inline int add(int x,int y){x+=y;return x>mod?x-mod:x;}
inline int dec(int x,int y){x-=y;return x<0?x+mod:x;}
inline int mul(LL x,int y){x*=y;return x>mod?x%mod:x;}

inline void init(){
    sum[1]=2;
    for(int i=2;i<=n;i++) sum[i]=mul(sum[i-1],2);
    for(int i=1;i<=n;i++) sum[i]=dec(sum[i],1);

    for(int i=1;i<=sqrt(P);i++) if(P%i==0) num[++cnt]=i;
    for(int i=cnt;i>1;i--) if(P/num[i]!=num[i]) num[++cnt]=P/num[i];
    for(int i=1;i<=n;i++){
        int pos=lower_bound(num+1,num+cnt+1,v[i])-num;
        tot[pos]++;//统计每个约数出现了几次,对应到后面选择的方案数
    }

    for(int i=1;i<=cnt;i++)if(tot[i]){
        now^=1;
        for(int j=1;j<=cnt;j++) f[now][j]=f[now^1][j];//不选:继承上一轮答案
        for(int j=1;j<=i;j++)if(f[now^1][j]){//选的方案数
            int pos=__gcd(num[i],num[j]);
            pos=lower_bound(num+1,num+cnt+1,pos)-num;
            f[now][pos]=add(f[now][pos],mul(f[now^1][j],sum[tot[i]]));//巧妙地组合起来转移
        }
        f[now][i]=add(f[now][i],sum[tot[i]]);//只选这一种
    }
    for(int i=1;i<=cnt;i++){
        for(int j=1;j<=i;j++)
            if(num[i]%num[j]==0) g[i]=add(g[i],f[now][j]);
    }
}

int main(){
    n=read(),Q=read(),P=read();
    for(int i=1;i<=n;i++) v[i]=__gcd((int)read(),P);
    init();
    while(Q--){
        int x=__gcd((int)read(),P);
        int pos=lower_bound(num+1,num+cnt+1,x)-num;
        assert(num[pos]==x);
        printf("%d\n",g[pos]);
    }
}

转载于:https://www.cnblogs.com/lizehon/p/10579665.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值