[Luogu P3321] [BZOJ 3992] [SDOI2015]序列统计

洛谷传送门
BZOJ传送门

题目描述

小C有一个集合 S S S,里面的元素都是小于 M M M的非负整数。他用程序编写了一个数列生成器,可以生成一个长度为 N N N的数列,数列中的每个数都属于集合 S S S。小C用这个生成器生成了许多这样的数列。但是小C有一个问题需要你的帮助:给定整数 x x x,求所有可以生成出的,且满足数列中所有数的乘积 m o d   M mod\ M mod M的值等于 x x x的不同的数列的有多少个。小C认为,两个数列 { A i } \{A_i\} {Ai} { B i } \{B_i\} {Bi}不同,当且仅当至少存在一个整数 i i i,满足 A i ≠ B i A_i≠B_i Ai̸=Bi。另外,小C认为这个问题的答案可能很大,因此他只需要你帮助他求出答案 m o d   1004535809 mod\ 1004535809 mod 1004535809的值就可以了。

输入输出格式

输入格式:

一行,四个整数, N N N M M M x x x ∣ S ∣ |S| S,其中 ∣ S ∣ |S| S为集合 S S S中元素个数。第二行, ∣ S ∣ |S| S个整数,表示集合 S S S中的所有元素。

输出格式:

一行,一个整数,表示你求出的种类数 m o d   1004535809 mod\ 1004535809 mod 1004535809的值。

输入输出样例

输入样例#1:
4 3 1 2
1 2
输出样例#1:
8

说明

【样例说明】

可以生成的满足要求的不同的数列有(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 1\le N\le 1000 1N1000

对于30%的数据, 3 ≤ M ≤ 100 3\le M\le 100 3M100

对于60%的数据, 3 ≤ M ≤ 800 3\le M\le 800 3M800

对于全部的数据, 1 ≤ N ≤ 1 0 9 1\le N\le 10^9 1N109 3 ≤ M ≤ 8000 3\le M\le 8000 3M8000 M M M为质数, 1 ≤ x ≤ M − 1 1\le x\le M-1 1xM1,输入数据保证集合 S S S中元素不重复

解题分析

看到 M M M是个小质数, 而我们要求的是一个余数, 显然可以转化为原根的次幂, 这样就将乘法转化为了加法。

发现方案的转移是一个卷积, 这样直接大力 N T T NTT NTT加上快速幂转移就好。

总复杂度 O ( M l o g ( M ) l o g ( N ) ) O(Mlog(M)log(N)) O(Mlog(M)log(N))

代码如下:

#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <cstring>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define MX 40050
#define G 3
#define Ginv 334845270
#define MOD 1004535809
template <class T>
IN void in(T &x)
{
	x = 0; R char c = gc;
	for (; !isdigit(c); c = gc);
	for (;  isdigit(c); c = gc)
	x = (x << 1) + (x << 3) + c - 48;
}
IN int fpow(R int base, R int tim, R int mod)
{
	int ret = 1;
	W (tim)
	{
		if (tim & 1) ret = 1ll * ret * base % mod;
		base = 1ll * base * base % mod, tim >>= 1;
	}
	return ret;
}
int N, M, x, S, fcnt, bd;
int rev[MX], res[MX], a[MX], b[MX], cnt[MX], fac[MX], pos[MX];
IN void NTT(int *dat, R int len, R bool typ)
{
	for (R int i = 0; i < len; ++i) if (rev[i] > i) std::swap(dat[i], dat[rev[i]]);
	R int seg, now, cur, step, bd, buf1, buf2, deal, base;
	for (seg = 1; seg < len; seg <<= 1)
	{
		base = fpow(typ ? G : Ginv, (MOD - 1) / (seg << 1), MOD); step = seg << 1;
		for (now = 0; now < len; now += step)
		{
			deal = 1, bd = now + seg;
			for (cur = now; cur < bd; ++cur, deal = 1ll * deal * base % MOD)
			{
				buf1 = dat[cur], buf2 = 1ll * dat[cur + seg] * deal % MOD;
				dat[cur] = (buf1 + buf2) % MOD, dat[cur + seg] = (buf1 - buf2 + MOD) % MOD;
			}
		}
	}
	if (typ) return; int inv = fpow(len, MOD - 2, MOD);
	for (R int i = 0; i < len; ++i) dat[i] = 1ll * dat[i] * inv % MOD;
}
void pre()
{
	int bd = std::sqrt(M - 1), tmp = M - 1;
	for (R int i = 2; i <= bd; ++i)
	{
		if (!(tmp % i))
		{
			fac[++fcnt] = i;
			if (i != tmp / i) fac[++fcnt] = tmp / i;
		}
	}
}
IN bool check(R int val)
{
	for (R int i = 1; i <= fcnt; ++i)
	if (fpow(val, fac[i], M) == 1) return false;
	return true;
}
IN void find()
{
	for (R int i = 2; i < M; ++i)
	if (check(i))
	{
		int bd = M - 1, now = 1;
		for (R int j = 0; j < bd; ++j, now = now * i % M) pos[now] = j;
		return;
	}
}
IN void Mul(int *res, int *mul, R int len)
{
	for (R int i = bd; i < len; ++i) a[i] = b[i] = 0;
	for (R int i = 0; i < bd; ++i) a[i] = res[i], b[i] = mul[i], res[i] = 0;
	NTT(a, len, 1), NTT(b, len, 1);
	for (R int i = 0; i < len; ++i) a[i] = 1ll * a[i] * b[i] % MOD;
	NTT(a, len, 0);
	for (R int i = 0; i < len; ++i) (res[i % bd] += a[i]) %= MOD;
}
int main(void)
{
	int foo;
	in(N), in(M), in(x), in(S);
	pre(); find(); bd = M - 1;
	for (R int i = 1; i <= S; ++i)
	{
		in(foo); foo %= M;
		if (!foo) continue;
		++cnt[pos[foo]];
	}
	int lg = 0, len = 1; --N;
	for (R int i = 0; i < bd; ++i) res[i] = cnt[i];
	for (; len <= (bd << 1); lg++, len <<= 1);
	for (R int i = 1; i < len; ++i) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << lg - 1);
	W (N)
	{
		if (N & 1) Mul(res, cnt, len);
		Mul(cnt, cnt, len), N >>= 1;
	}
	printf("%d", res[pos[x]]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值