前言
UPD on 2021.1.7 更新了 NTT 做法:https://www.cnblogs.com/stoorz/p/14248727.html
不会\(\operatorname{FFT,NTT}\),水一个\(O(m^2\log n)\)的\(60pts\),大力卡常过的。
GMOJ的时限是\(5s\),洛谷的时限\(1s\)完全过不了。
题目
题目链接:https://gmoj.net/senior/#main/show/4051
小C有一个集合 \(S\),里面的元素都是小于 \(m\) 的非负整数。他用程序编写了一个数列生成器,可以生成一个长度为 \(n\) 的数列,数列中的每个数都属于集合 \(S\)。
小C用这个生成器生成了许多这样的数列。但是小C有一个问题需要你的帮助:给定整数 \(x\),求所有可以生成出的,且满足数列中所有数的乘积 \(\bmod \ m\) 的值等于 \(x\) 的不同的数列的有多少个。
小C认为,两个数列 \(A\) 和 \(B\) 不同,当且仅当 \(\exists i \text{ s.t. } A_i \neq B_i\)。另外,小C认为这个问题的答案可能很大,因此他只需要你帮助他求出答案对 \(1004535809\) 取模的值就可以了。
思路
设\(f[i][j]\)表示选了\(2^i\)个数,乘积\(\bmod p\)之后的结果为\(j\)的方案数。
转移显然
\[f[k][l]=\sum^{}_{i\times j\bmod p=l}f[k-1][i]\times f[k-1][j]
\]
然后设\(n=\sum^{30}_{i=1}x_i2^i\),按照二进制拆分计算方案数即可。
时间复杂度\(O(m^2\log n)\)。
利用矩阵乘法,同样的思路复杂度为\(O(m^3\log n)\),期望得分\(30pts\)。
代码
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline") //卡常必备
#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=8010,MOD=1004535809/*,LG=30*/;
int n,p,m,t,cnt;
ll ans,f[50][N],g[50][N];
int main()
{
scanf("%d%d%d%d",&n,&p,&t,&m);
int LG=log2(n);
for (register int i=1,x;i<=m;i++)
{
scanf("%d",&x);
f[0][x]++;
}
for (register int k=1;k<=LG;++k)
for (register int i=0;i<p;++i)
for (register int j=0,o=0;j<p;++j,o+=i) // o用来减少取模次数
{
if (o>=p) o-=p;
f[k][o]=(f[k][o]+f[k-1][i]*f[k-1][j])%MOD;
}
g[0][1]=1;
for (register int k=0;k<=LG;k++)
if (n&(1<<k))
{
cnt++;
for (register int i=0;i<p;i++)
for (register int j=0,o=0;j<p;j++,o+=i)
{
if (o>=p) o-=p;
g[cnt][o]=(g[cnt][o]+g[cnt-1][i]*f[k][j])%MOD;
}
}
printf("%lld\n",g[cnt][t]);
return 0;
}