bzoj3992 [SDOI2015]序列统计(从一道题入手NTT)

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 2
1 2

Sample Output
8

HINT
【样例说明】
可以生成的满足要求的不同的数列有(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)。

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

分析:
先报算法:NTT
这种东西的思路都是
找多项式,看出是卷积形式,然后套板子

好像做过一道很像的题呢
那道题是叫你统计n个数相加,模数一定的方案数

那我们第一件事就是想dp啊
f[i][j]=sigma f[i-1][j*inv[k]] //inv是逆元

这个复杂度显然不科学
我们先放下这个问题,看一种新的算法:

FNT/NTT

这里写图片描述
这里写图片描述
这里写图片描述

诶诶诶
这个模数怎么这么熟悉
1004535809
那这道题我们要是能找到卷积的形式
就可以套NTT板子了

归根到底
g^(p-1)同余于1(mod p)
则称g是p的原根

求原根目前的做法只能是从2开始枚举,
然后暴力判断g^(p-1)同余于1 (mod p)
是否当且仅当指数为p-1的时候成立
而由于原根一般都不大,所以可以暴力得到

当然一些常见的模数是要记住的
像这道题中,1004535809就是一个原根为3的经典模数

找到了学姐的blog,讲的蛮不错
http://blog.csdn.net/clover_hxy/article/details/56495911

我在这里说一下自己的理解:

原根的次方可以表示p以内的任意数字,因为是模意义下的运算
我们当然可以用原根的任意次幂表示任意数字(mod p)
这样我们可以把之前的乘法变成一个加法

新的状态就变成了

f[i][j]表示选到第i个数,结果是g^j的方案数

f[i][j+k]+=f[i-1][j] //同底数幂相乘,底数不变,指数相加

用h[i]表示原根的i次幂的方案数,q[i]表示原根的i次幂是否在集合中

h[i]=sigma h[j]*q[i-j]

这就是一个卷积的形式了

tip

主程序中是正常的预处理
fn很容易带成n

原根的求法觉得很值得记下:
这里写图片描述

为了快速处理
我们用了一个类似于快速幂的东西

mul的过程和FFT很像,唯一需要注意的是,最后在/fn的时候
要处理出一个逆元,化除为乘

NTT的过程和FFT基本一样

这是分割线———————————————————————————————-

查了半天错,才发现是自己定义的一个swap函数
没有带取地址符。。。orz

在NTT函数中,变量名老是搞错
现在才明白执行语句顺序的重要性
以前写FFT的时候,
a[m+j+k]的操作总是在a[j+k]之前,我也没怎么注意
结果这次顺序写反,就jj了

没有好好读题
题目是让在S中选择n个数,S中每个数的大小都不超过m,个数是tot

在传参进入slove的时候,应该传n,QAQ

那么卷机过后的数组长度就是2*(m-1)

WA.st
快速幂少%了一下
求原根的时候KSM(i,(x-1)/j,x)
WA.nd
lld<–I64d

总觉得这道题真的是很艰难的A了

这里写代码片
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#define ll long long

using namespace std;

const int N=17000;
const ll mod=1004535809;
int n,m,X,tot,yg,M,fn,R[N];   
bool vis[N];
ll h[N],a[N],b[N],q[N];

ll KSM(ll a,ll b,ll p)
{
    a%=p;
    ll t=1;
    while (b)
    {
        if (b&1)
           t=(t*a)%p;
        a=(a*a)%p;
        b>>=1;
    }
    return t%p;
}

int calc(int x)   //求原根
{
    if (x==2) return 1;
    for (int i=2;i;i++)
    {
        bool pp=1;
        for (int j=2;j*j<x;j++)
            if (KSM(i,(x-1)/j,x)==1)   
            //只要有一个非mod-1的数幂也同余于1,就不合法 
            {
                pp=0;
                break;
            }
        if (pp) return i;
    }
} 

void NTT(ll *a,int n,int opt)
{
    int i,j=0,k;
    for (i=0;i<n;i++)
    {
        if (i>j) swap(a[i],a[j]);
        for (int l=n>>1;(j^=l)<l;l>>=1);
    }
    for (i=1;i<n;i<<=1)
    {
        ll wn=KSM(3,(mod-1)/(i<<1),mod);  //1004535809原根 
        int m=i<<1;
        for (j=0;j<n;j+=m)
        {
            ll w=1;
            for (k=0;k<i;k++,w=(w*wn)%mod)
            {
                ll z=(w*a[j+k+i])%mod;
                a[j+k+i]=(a[j+k]-z+mod)%mod;
                a[j+k]=(a[j+k]+z)%mod;
            }
        }
    }
    if (opt==-1) reverse(a+1,a+n);
} 

void mul(ll h[N],ll q[N])
{
    for (int i=0;i<fn;i++) a[i]=h[i]%mod;
    for (int i=0;i<fn;i++) b[i]=q[i]%mod;
    NTT(a,fn,1); NTT(b,fn,1);
    for (int i=0;i<fn;i++) a[i]=(a[i]*b[i])%mod;
    NTT(a,fn,-1);
    ll inv=KSM(fn,mod-2,mod);   //逆元
    for (int i=0;i<fn;i++) a[i]=(a[i]*inv)%mod;  
    //因为是模意义下的整数除法,就不能像FFT一样这么耿直的直接/了 
    for (int i=0;i<m-1;i++)
        h[i]=(a[i]+a[i+m-1])%mod; 
}

void solve(int x)
{
    h[0]=1;
    while (x)   //类似一个快速幂,快速处理卷积 
    { 
        if (x&1) mul(h,q);
        x>>=1;
        mul(q,q); 
    }
}

int main()
{
    scanf("%d%d%d%d",&n,&m,&X,&tot);
    yg=calc(m);
    for (int i=1;i<=tot;i++)
    {
        int x;
        scanf("%d",&x);vis[x]=1;
    } 
    int pos=-1;
    for (int i=0,j=1;i<m-1;i++,j=(j*yg)%m)
    {
        if (vis[j]) q[i]=1;   //原根的i次幂在不在集合中 
        if (j==X) pos=i;   //答案是yg的pos次幂 
    }
    M=(m-1)*2; fn=1;
    while (fn<=M) fn<<=1;
    solve(n);
    if (pos!=-1) printf("%lld
",h[pos]%mod);
    else printf("0
");
    return 0;
}
原文地址:https://www.cnblogs.com/wutongtong3117/p/7673370.html