【GMOJ4051】序列统计

前言

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;
}
原文地址:https://www.cnblogs.com/stoorz/p/12308065.html