题目大意
给定集合\(S\),里面的元素都是小于\(M\)的非负整数。
然后生成一个长度为\(N\)的数列,数列中的每个数都属于集合\(S\)。
现在的问题是:给定整数\(x\),
求所有可以生成出的,且满足数列中所有数的乘积\(mod\ M\)的值等于\(x\)的 不同的数列的有多少个。
答案对\(1004535809\)取模。
样例输入:
\(4\ \ \ 3\ \ \ 1\ \ \ 2\)
\(1\ \ \ 2\)
样例输出:
\(8\)
样例解释:请自己yy
数据范围:\(1\leq N\leq10^9\),\(3\leq M\leq 8000\),\(M\)为质数,\(1\leq x\leq M-1\) ;
思路与解法
高神推荐的动规题目,果然带有浓重的数学色彩......
首先最朴素的\(DP\)大家都会:
\(f[i][j]\)表示做到了序列的第\(i\)个位置,乘积为\(j\)的方案数,转移:。
\[f[i][\ \ (j*s[x]\%M)\ \ ] =\sum f[i-1][j]\]
其中\(s[x]\)是\(S\)中的一个元素。然而这样就没法优化了......
所以我们要引入原根的概念(不是很早以前就引入了吗?这里回顾一下):
一个数\(p\)的原根\(g\),它满足这样一个性质:
\(r(i) = g^i \% p\) , 当\(i \in [0,p-2]\)时,\(r(i)\)取遍 \([1,p-1]\)。
怎么求怎么用之类的网上很多自己看(其实是我懒得写),给一下模板:戳我!
所以我们把\(a * b\equiv x\ (mod \ p)\)中的\(a\)、\(b\)、\(x\)都换成\(g^i\)。
然后给每一个\(g^i\)与\(i\)建立映射关系。
那么原来的(\(a*b=x\))\(即(g^i*g^j = g^k)\)就变为了\((i+j=k)\)的加法运算。
这个有什么用呢? 重写一下之前的\(DP\)式:
设\(f[i][j]\)表示做到了序列的第\(i\)个位置,状态为\(j\)(\(j\)是\(g^t\)中的一个\(t\))的方案数。
我们把状态第一维舍去,写出方程式:
\[f[\ (j_1+j_2)\%(p-1)\ ] = \sum f[ j_1 ]*exist(j_2)\]
其中\(exist(j_2)\)表示的是\(g^{j_2}\)在\(S\)中是否存在,显然可以预处理。
上面那个是标准卷积形式( \(\ \sum f(i)*\sum exist(j)\ \) ),直接快速幂加\(NTT\)一波即可。
实现代码:
注:本题数据极坑,请自觉把集合\(S\)中的\(0\)元素扔掉。
#include<bits/stdc++.h>
#define RG register
#define IL inline
#define ll long long
#define mod 1004535809
#define gi(xx) scanf("%lld",&xx)
#define _ 1000005
using namespace std;
ll f[_] , exist[_] , f1[_] , f2[_]; int prm[_],id[_];
ll n , m , s, x , l , tot , R[_] , len , ef; ll g , wn[30];
IL ll Pow(RG ll T,RG ll js,RG ll S,RG ll MOD){
while(js){
if(js & 1)S = (S * T) % MOD;
T = T * T % MOD; js >>= 1;
}return S;
}//快速幂
IL void Get_Pr_Root(){
RG ll tmp = m - 1;
for(RG int i = 2; i <= sqrt(tmp); i ++){
if(tmp % i == 0){
while(tmp % i == 0)tmp /= i;
prm[ ++ tot ] = i;
}}
if(tmp ^ 1)prm[ ++ tot] = tmp;
for(g = 2; g <= m - 1; g ++){
RG bool flag = true;
for(RG int i = 1; i <= tot; i ++)
if(Pow( g , (m-1)/prm[i] , 1 , m) == 1)
{ flag = false ; break; }
if(flag)break;
}return;
}//求解m的原根
IL void Give_ID(){
for(RG int i = 0; i < m - 1; i ++){
RG ll data = Pow( g , i , 1 , m);
id[ data ] = i;
}return;
}//建立对应映射关系
const ll pr = 3; //3是1004535809 的原根
IL void Pre(){
len = 2 * (m-2);
for(ef = 1; ef <= len; ef<<=1) ++ l;
for(RG int i = 0; i < ef; i ++)
R[i] = (R[i>>1] >> 1) | ((i&1) << (l-1));
for(RG int i = 0; i < 25; i ++){
RG ll tt = (1<<i);
wn[i] = Pow(pr , (mod-1) / tt , 1 , mod);
}return;
}//NTT的准备工作
IL void NTT(RG ll *P , RG ll opt){
for(RG int i = 0; i < ef; i ++)
if(i < R[i]) swap(P[i] , P[R[i]]);
for(RG int i = 1,id =0; i < ef; i <<= 1){
++ id;
for(RG int j = 0,p = i<<1 ; j < ef; j += p){
RG ll w = 1;
for(RG int k = 0; k < i; k ++,w = w*wn[id]%mod){
RG ll X = P[j + k] , Y = w*P[j + k + i];
P[j + k] = (X + Y) % mod;
P[j + k + i] = ((X - Y)%mod + mod) % mod;
}}}
if(opt < 0){
for(RG int i = 1; i < ef/2; i ++)swap(P[i] , P[ef-i]);
RG ll inv = Pow(ef , mod - 2, 1 , mod);
for(RG int i = 0; i < ef; i ++)P[i] = P[i] % mod * inv % mod;
}return;
}//NTT计算卷积
IL void Mul(RG ll *ans , RG ll *itm){
for(RG int i = 0; i < ef; i ++)f1[i] = ans[i];
for(RG int i = 0; i < ef; i ++)f2[i] = itm[i];
NTT(f1,1); NTT(f2,1);
for(RG int i = 0; i < ef; i ++)f1[i] = (f1[i] * f2[i]) % mod;
NTT(f1,-1);
for(RG int i = 0; i < m-1; i ++)
ans[i] = ( f1[i] + f1[i+m-1] ) % mod;
return;
}//进行DP转移
IL void Work(){
Pre(); f[id[1]] = 1;
while( n ){
if(n & 1)Mul(f , exist);
Mul(exist , exist); n >>= 1;
}return;
}//快速幂求解答案f
int main(){
gi(n); gi(m); gi(x); gi(s);
Get_Pr_Root();
Give_ID();
RG ll si; while( s -- ){ gi(si); if(si)exist[id[si]] = 1; }
Work();
printf("%lld" , f[id[x]]); return 0;
}