SDOI2015 序列统计

Description

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

Input

一行,四个整数,N、M、x、|S|,其中|S|为集合S中元素个数。
第二行,|S|个整数,表示集合S中的所有元素。

Output

一行,一个整数,表示你求出的权值和mod 1004535809的值。

Sample Input

4 3 1 21 2

Sample Output

8【样例说明】可以生成的满足要求的不同的数列有(1,1,1,1)、(1,1,2,2)、(1,2,1,2)、(1,2,2,1)、(2,1,1,2)、(2,1,2,1)、(2,2,1,1)、(2,2,2,2)。

Data Constraint

对于10%的数据,1<=N<=1000;
对于30%的数据,3<=M<=100;
对于60%的数据,3<=M<=800;
对于全部的数据,1<=N<=109,3<=M<=8000,M为质数,1<=x<=M-1,输入数据保证集合S中元素不重复。

解法:

首先可以想到一个暴力dp,设f[i][j]表示选到第i个数,mod m=j的方案数。那么显然转移方程为f[i+1][j*k%m]+=f[i][j];

即f[i+1][j*k%m]=∑f[i][j]*num[k](序列S中mod m=k的数的个数)

因为m是质数,所以m存在原根,我们可以通过离散对数的变换将乘法变为加法

原式变为f[i+1][(ind[j]+ind[k])%(m-1)]=∑f[i][ind[j]]*num[ind[k]]

为卷积形式,可使用FFT优化。

N很大,所以套一个快速幂即可

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

using namespace std;
typedef long long ll;

int n,m,x,L,i,k,N,ws,NN;
int a[100011],pos[100011],b[100011],B[100011],d[100011];
int w[100011],ind[100011],h[100011];
int mo=1004535809;

int mi(int x,int z)
{
    int l;
    l=1;
    while(z){
        if(z%2==1)l=(ll)l*x%mo;
        z/=2;
        x=(ll)x*x%mo;
    }
    return l;
}

bool isroot(int x)
{
    int i,l;
    l=1;
    for(i=1;i<m-1;i++){
        l=l*x%m;
        if(l==1)return false;
    }
    return true;
}

void prepare()
{
    int i,j,k,g,l;
    //离散对数
    for(i=1;i<m;i++)if(isroot(i)){
        g=i;
        break;
    }
    l=1;
    for(i=1;i<m;i++){
        l=l*g%m;
        ind[l]=i;
    }
    //fftN次单位复数根
    N=1;
    while(N<2*m){
        N*=2;
        ws++;
    }
    w[0]=1;
    w[1]=mi(3,(mo-1)/N);
    for(i=2;i<=N;i++)w[i]=(ll)w[i-1]*w[1]%mo;
    for(i=0;i<N;i++){
        for(j=0;j<ws;j++){
            k=(i&(1<<(ws-1-j)));
            if(k)pos[i]+=(1<<j);
        }
    }
    NN=mi(N,mo-2);
}

void Dft(int *a,int sig)
{
    int i,half,u,v,j,l,wi;
    for(i=0;i<N;i++)h[pos[i]]=a[i];
    for(l=1;l<=ws;l++){
        half=1<<(l-1);
        for(i=0;i<half;i++){
            wi=(sig>0)?w[i<<(ws-l)]:w[N-(i<<(ws-l))];
            for(j=i;j<N;j+=(1<<l)){
                u=h[j];v=(ll)h[j+half]*wi%mo;
                h[j]=(u+v)%mo;
                h[j+half]=((u-v)%mo+mo)%mo;
            }
        }
    }
    for(i=0;i<N;i++)a[i]=h[i];
}

void fft(int *a,int *b)
{
    int i;
    Dft(a,1);
    Dft(b,1);
    for(i=0;i<N;i++)a[i]=(ll)a[i]*b[i]%mo;
    Dft(a,-1);
    for(i=0;i<N;i++)a[i]=(ll)a[i]*NN%mo;
    for(i=m;i<N;i++){
        a[(i%(m-1)==0)?(m-1):(i%(m-1))]=(a[(i%(m-1)==0)?(m-1):(i%(m-1))]+a[i])%mo;
        a[i]=0;
    }
}

void MT(int *b,int z)
{
    int i;
    d[0]=1;
    while(z){
        if(z%2==1){
            for(i=0;i<N;i++)B[i]=b[i];
            fft(d,b);
            for(i=0;i<N;i++)b[i]=B[i];
        }
        z/=2;
        for(i=0;i<N;i++)B[i]=b[i];
        fft(b,B);
    }
}

void Work()
{
    a[ind[1]]++;
    MT(b,n);
    fft(a,d);
    printf("%d\n",a[ind[x]]);
}

int main()
{
    scanf("%d%d%d%d",&n,&m,&x,&L);
    prepare();
    for(i=1;i<=L;i++){
        scanf("%d",&k);
        k%=m;
        b[ind[k]]++;
    }
    b[0]=0;
    Work();
}
时间: 2024-10-10 21:17:52

SDOI2015 序列统计的相关文章

BZOJ 3992: [SDOI2015]序列统计 NTT+快速幂

3992: [SDOI2015]序列统计 Time Limit: 30 Sec  Memory Limit: 128 MBSubmit: 1155  Solved: 532[Submit][Status][Discuss] Description 小C有一个集合S,里面的元素都是小于M的非负整数.他用程序编写了一个数列生成器,可以生成一个长度为N的数列,数列中的每个数都属于集合S. 小C用这个生成器生成了许多这样的数列.但是小C有一个问题需要你的帮助:给定整数x,求所有可以生成出的,且满足数列中

BZOJ 3992: [SDOI2015]序列统计 [快速数论变换 生成函数 离散对数]

3992: [SDOI2015]序列统计 Time Limit: 30 Sec  Memory Limit: 128 MBSubmit: 1017  Solved: 466[Submit][Status][Discuss] Description 小C有一个集合S,里面的元素都是小于M的非负整数.他用程序编写了一个数列生成器,可以生成一个长度为N的数列,数列中的每个数都属于集合S. 小C用这个生成器生成了许多这样的数列.但是小C有一个问题需要你的帮助:给定整数x,求所有可以生成出的,且满足数列中

【BZOJ3992】[SDOI2015]序列统计 NTT+多项式快速幂

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

BZOJ 3992: [SDOI2015]序列统计 快速幂+NTT(离散对数下)

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

bzoj3992 [SDOI2015]序列统计

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

P3321 [SDOI2015]序列统计 FFT+快速幂+原根

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

[bzoj3992][SDOI2015]序列统计——离散对数+NTT

题目大意: 给定一个数字不超过\(m\)的集合\(S\),用\(S\)中的数生成一个长度为\(n\)的序列,求所有序列中的元素乘积模\(m\)等于\(x\)的序列的个数. 思路: 考虑最朴素的\(DP\),设\(f_{i,j}\)为选了\(i\)个数,乘积模\(m\)余\(j\)的方案数,直接转移的时间复杂度是\(O(nm^2)\)的. 不难发现每次转移的过程是相同的,矩阵加速显然不太可行,考虑将乘法形式的转移变成加法形式的转移,这样每次转移即可用NTT优化. 这里需要用到一个叫做离散对数的东西

bzoj 3992: [SDOI2015]序列统计

膝盖++,IQ-- SD总是酱紫.....吐槽+++++++ 这个乘积的形式是可以用他的原根表示成加法的!!神奇啊!!! 然后加法就很棒棒了,我们可以用生成函数这个东西来计算一下了. 然后NTT就好了!! 还有这里有一个像快速幂的东西,而且把大于模数的东西搞小,是循环卷积的形式吗??好神奇啊 原根真的是劲啊 1 #include <cstdio> 2 #include <iostream> 3 #include <cmath> 4 #define LL long lon

【动态规划】bzoj3992 [Sdoi2015]序列统计 10分

#include<cstdio> using namespace std; #define MOD 1004535809 int a[8001],f[1001][101],n,m,x,S; int main() { scanf("%d%d%d%d",&n,&m,&x,&S); for(int i=1;i<=S;++i) { scanf("%d",&a[i]); a[i]%=m; ++f[1][a[i]]; }