【牛客挑战赛45】G.致两千年后的你

32 篇文章 1 订阅
4 篇文章 0 订阅

Description

在这里插入图片描述
在这里插入图片描述

Solution

  • 首先一个集合 S S S可以把 g c d ( a 1 , a 2 , a 3 , . . . , P ) gcd(a_1,a_2,a_3,...,P) gcd(a1,a2,a3,...,P)的倍数拼出。
  • 因此我们把 P , x , a i P,x,a_i P,x,ai分解质因数,把 a i a_i ai根据每一个质因数的次数是否大于 x x x的次数作为二进制数,然后高位后缀和一下,就可以知道某个 T T T的倍数的个数。
  • 考虑转移 k k k次对于 f 0 ( S ) f_0(S) f0(S)的贡献实际上是 k n k ∣ S ∣ \frac{k^n}{k^{|S|}} kSkn,因此一个 S S S的贡献即为 1 ∣ S ∣ \frac{1}{|S|} S1
  • 假设某一个数的倍数个数为 c n t cnt cnt(只分辨每一个质因数次数是否大于 x x x),那么这个位置的答案为 ( 1 k + 1 ) c n t − 1 (\frac{1}{k}+1)^{cnt}-1 (k1+1)cnt1 ,然后高位差分回去得到的 f ( 0 ) f(0) f(0)就是答案了。
  • 有点卡常。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
#define mo 1000000007
#define maxn 300005
#define maxm 20
using namespace std;
 
int T,n,i,j,k,c[maxm],mul[maxm],p[maxm],cnt,cx[maxm],_2[maxm];
ll a[maxn],f[maxn],inv,mulk[maxn],ans,P,x,K;
 
ll gcd(ll x,ll y){return (x%y==0)?y:gcd(y,x%y);}
ll ksm(ll x,ll y){
    ll s=1;
    for(;y;y/=2,x=x*x%mo) if (y&1)
        s=s*x%mo;
    return s;
}
int tot,pri[maxn],bz[maxn];
void getpri(){
    for(i=2;i<maxn;i++) {
        if (!bz[i]) pri[++tot]=i;
        for(j=1;j<=tot&&pri[j]*i<maxn;j++){
            bz[i*pri[j]]=1;
            if (i%pri[j]==0) break;
        }
    }
}
 
int main(){
	freopen("ceshi.in","r",stdin);
    getpri();for(i=0;i<maxm;i++) _2[i]=1<<i;
    scanf("%d%d",&n,&T);
    for(i=1;i<=n;i++) scanf("%lld",&a[i]);
    while (T--){
        scanf("%lld%lld%lld",&x,&K,&P),inv=ksm(K,mo-2);
        mulk[0]=1; for(i=1;i<=n;i++) mulk[i]=mulk[i-1]*(inv+1)%mo;
        cnt=0;
        for(i=1;i<=tot;i++) if (P%pri[i]==0){
            ll tmp=P; cnt++,p[cnt]=pri[i],c[cnt]=0;
            while (tmp%pri[i]==0) c[cnt]++,tmp/=pri[i];
        }
        if (x%P==0) x=P; else x=x%P;
        for(i=1;i<=cnt;i++) {
            cx[i]=0;
            while (x%p[i]==0) cx[i]++,x/=p[i];
        }
        memset(f,0,sizeof(f));
        for(i=1;i<=n;i++) {
            int s=0; ll tmp=a[i];
            for(j=1;j<=cnt&&tmp>1;j++) if (tmp%p[j]==0){
                int cc=0;
                while (tmp%p[j]==0&&cc<c[j]) tmp/=p[j],cc++;
                if (cc>cx[j]) s+=1<<j-1;
            }
            f[s]++;
        }
		for(i=0;i<cnt;i++) for(j=_2[cnt]-1;j>=0;j--) if (j>>i&1)
			f[j^_2[i]]+=f[j];
		for(i=0;i<_2[cnt];i++) f[i]=mulk[f[i]]-1;
		for(i=0;i<cnt;i++) for(j=0;j<_2[cnt];j++) if (j>>i&1)
			(f[j^_2[i]]-=f[j])%=mo;
        printf("%lld\n",(f[0]*ksm(K,n)%mo+mo)%mo);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值