【JZOJ 4051】【SDOI2015第1轮第1试】序列统计

本文介绍了一种使用快速傅立叶变换/数论变换解决数列生成问题的方法。该问题要求计算满足特定乘积条件的不同数列的数量。通过将乘法转换为幂次加法,将问题转化为多项式乘法,再利用FFT/NNT加速计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

小C有一个集合S,里面的元素都是小于M的非负整数。他用程序编写了一个数列生成器,可以生成一个长度为N的数列,数列中的每个数都属于集合S。
小C用这个生成器生成了许多这样的数列。但是小C有一个问题需要你的帮助:给定整数x,求所有可以生成出的,且满足数列中所有数的乘积mod M的值等于x的不同的数列的有多少个。小C认为,两个数列{Ai}和{Bi}不同,当且仅当至少存在一个整数i,满足Ai≠Bi。另外,小C认为这个问题的答案可能很大,因此他只需要你帮助他求出答案mod 1004535809的值就可以了。

Solution

FFT/NNT练习题目
解法是先把暴力Dp式子写出来,因为里面有乘法,把乘法转成幂次的加,这样就变成了多项式乘法了,
当然要求原根

复杂度:O(log(N)Mlog(M))

Code

#include <cstdio>
#define fo(i,a,b) for(int i=a;i<=b;++i)
using namespace std;
typedef long long LL;
const int N=8500,mo=1004535809;
int read(int &n)
{
    char ch=' ';int q=0,w=1;
    for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
    if(ch=='-')w=-1,ch=getchar();
    for(;ch>='0' && ch<='9';ch=getchar())q=q*10+ch-48;n=q*w;return n;
}
int m,n,n1,m1,R;
int a[N];
LL ans;
LL f[N*4],Ans[N*4],c[N*4];
LL W[N*4],W0,niy;
LL ksm(LL q,int w,int MO=mo)
{
    LL ans=1;q%=MO;
    for(;w;w>>=1,q=q*q%MO)if(w&1)ans=ans*q%MO;
    return ans;
}
LL ROK(int q,int n)
{
    int w=n-1;
    for(int i=2;i*i<=w;++i)if(w%i==0)
    {
        for(;w%i==0;w=w/i);
        if(1==ksm(q,(n-1)/i,n))return 0;
    }
    if(w-1)return 1!=ksm(q,(n-1)/w,n);
    return 1;
}
void DFT(LL *a,int n,int ws,int K)
{
    fo(i,0,n-1)
    {
        int q=0;
        for(int j=i,w=ws;w;w--,j>>=1)q=(q<<1)+(j&1);
        c[q]=a[i];
    }
    for(int I=2;I<=n;I<<=1)
    {
        int mid=I>>1;
        fo(i,0,mid-1)
        {
            LL w=(K>0)?(W[W0/I*i]):(W[W0-W0/I*i]);
            for(int j=i;j<n;j+=I)
            {
                LL t=w*c[j+mid]%mo;
                c[j+mid]=(c[j]-t)%mo;
                c[j]=(c[j]+t)%mo;
            }
        }
    }
    if(K<0)fo(i,0,n-1)c[i]=c[i]*niy%mo;
}
void FFTpre()
{
    int m,ws;
    for(m=1,ws=1;m<n;m<<=1,ws++);
    m<<=1;
    DFT(f,m,ws,1);
    fo(i,0,m)f[i]=c[i]*c[i]%mo;
    DFT(f,m,ws,-1);
    fo(i,0,n-1)f[i]=c[i];
    fo(i,n,m)f[i%n]=(f[i%n]+c[i])%mo,f[i]=0;
}
void FFT()
{
    int m,ws;
    for(m=1,ws=1;m<n;m<<=1,ws++);
    m<<=1;
    DFT(Ans,m,ws,1);
    fo(i,0,m)Ans[i]=c[i];
    DFT(f,m,ws,1);
    fo(i,0,m)Ans[i]=Ans[i]*c[i]%mo;
    DFT(Ans,m,ws,-1);
    fo(i,0,n-1)Ans[i]=c[i];
    fo(i,n,m)Ans[i%n]=(Ans[i%n]+c[i])%mo,Ans[i]=0;
}
void ksm(int w)
{
    Ans[0]=1;
    for(;w;w>>=1,FFTpre())if(w&1)FFT();
}
int main()
{
    freopen("!.in","r",stdin);
//  freopen(".out","w",stdout);
    int q;
    read(n1),read(n),read(m),read(m1);
    fo(i,1,m1)a[read(q)%n]++;
    fo(i,2,n-1)if(ROK(i,n)){R=i;break;}
    --n;q=1;
    fo(i,0,n-1)f[i]=a[q],q=q*R%(n+1);
    for(W0=1;W0<n;W0<<=1);W0<<=1;
    W[0]=1;W[1]=ksm(3,(mo-1)/W0);niy=ksm(W0,mo-2);
    fo(i,2,W0)W[i]=W[i-1]*W[1]%mo;
    ksm(n1);
    ans=0;
    q=1;
    fo(i,0,n-1)
    {
        if(q==m)ans+=Ans[i];
        q=q*R%(n+1);
    }
    printf("%lld\n",(ans+mo)%mo);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值