您的位置:首页 > 其它

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

2017-08-26 21:11 417 查看
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
;
bool vis
;
ll h
,a
,b
,q
;

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
,ll q
)
{
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\n",h[pos]%mod);
else printf("0\n");
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: