bzoj3992: [SDOI2015]序列统计

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中元素不重复

知识点:快速数论变换

其实学了FFT之后再学NTT已经只差临门一脚了。
首先复习一下FFT

复习FFT

离散型快速傅立叶变换如下
Xk=∑n=0N−1xne−2πiNnkk=0,1⋯N−1 X k = ∑ n = 0 N − 1 x n e − 2 π i N n k k = 0 , 1 ⋯ N − 1 X_k=\sum\limits_{n=0}^{N-1}x_ne^{-\frac{2\pi i}{N}nk} k=0,1\cdots N - 1
离散型快速傅立叶逆变换如下
xn=1N∑n=0N−1Xke2πiNnkk=0,1⋯N−1 x n = 1 N ∑ n = 0 N − 1 X k e 2 π i N n k k = 0 , 1 ⋯ N − 1 x_n=\frac{1}{N}\sum\limits_{n=0}^{N-1}X_ke^{\frac{2\pi i}{N}nk} k=0,1\cdots N - 1

引入NTT

如今快速数论变换是在 Zp Z p Z_p上进行的。
快速傅立叶变换中,有一个神奇的单位复根叫做
ω=e−2πiN ω = e − 2 π i N \omega=e^{-\frac{2\pi i}{N}}
这玩意儿有一个神奇的性质,就是 ωN=1 ω N = 1 \omega^N=1并且 ωk ω k \omega^k当 k=0,1⋯N−1 k = 0 , 1 ⋯ N − 1 k=0,1\cdots N-1有N个取值。
那么相应地类比一下,在快速数论变换中,也有一个神奇的东西叫做g人家是正儿八经的原根。它的性质就是
e−2πiN≡gP−1N(modP) e − 2 π i N ≡ g P − 1 N ( mod P ) e^{-\frac{2\pi i}{N}}\equiv g^{\frac{P-1}{N}}(\mod P)
e这玩意儿不是double的吗,怎么跟一个int扔过去同余了捏,其实上面那个公式的意思,就是g再modp意义下和单位复根是同一个作用。。。。

离散型NTT公式

这样子的话,我们就可以得到我们的离散型快速数论变换的公式。
Xk=∑n=0N−1xngP−1Nnk(modP)k=0,1⋯N−1 X k = ∑ n = 0 N − 1 x n g P − 1 N n k ( mod P ) k = 0 , 1 ⋯ N − 1 X_k=\sum\limits_{n=0}^{N-1}x_ng^{\frac{P-1}{N}nk} (\mod P)k=0,1\cdots N - 1
类比一下就是离散型快速数论逆变换公式
Xk=1N∑n=0N−1xng−P−1Nnk(modP)k=0,1⋯N−1 X k = 1 N ∑ n = 0 N − 1 x n g − P − 1 N n k ( mod P ) k = 0 , 1 ⋯ N − 1 X_k=\frac{1}{N}\sum\limits_{n=0}^{N-1}x_ng^{-\frac{P-1}{N}nk} (\mod P)k=0,1\cdots N - 1
这个时候你肯定会觉得很草率,因为g是什么你都不知道。
所以说,这篇文章的重点是类比,是类比,是类比!!
你瞧,咱们的 ω ω \omega小盆友,可以它如果玩起次方来可以玩遍所有数,那么g同学也一样,它玩起次方来,也可以玩遍所有数。

原根的定义

标准的定义就是
设m是正整数,a是整数,若a模m的阶等于 ϕ(m) ϕ ( m ) \phi(m),则称a为模m的一个原根。
可是这个神奇的原根是不一定存在的,所以题目在大多数条件下给的模数是素数。有一个神奇的定理是,素数的原根一定存在。
我们发现我们的 P−1N P − 1 N \frac{P-1}{N}也不一定是整数。而我们知道,NTT中 N=2k N = 2 k N=2^k所以说,咱们的素数一般就是 k2n+1 k 2 n + 1 k2^n+1也就是费马素数。
这个时候NTT又变了一个名字,叫做费马数数论变换(什么?英文?英文是不存在的)
这个时候就可记几个常见的模数了
1004535809=479×221+1 1004535809 = 479 × 2 21 + 1 1004535809=479\times 2^{21}+1注意这个数的原根是3
998244353=119×223+1 998244353 = 119 × 2 23 + 1 998244353=119\times2^{23}+1这个数的原根仍然是3

寻根之旅

这个时候好奇的同学们就又会问了,如果题目不给你原根怎么办?为了考虑这些同学们的心情,就再扔一个定理来压压惊。
原根不会超过素数的 14 1 4 \frac{1}{4}次方(证明?证明是不存在的)
由原根的定义知
∀0<i,j<P,i≠j有gi≡/gj(modP) ∀ 0 < i , j < P , i ≠ j 有 g i ≢ g j ( mod P ) \forall 0并且 gp−1≡1(modP) g p − 1 ≡ 1 ( mod P ) g^{p-1}\equiv 1(\mod P)
转化一下可得当且仅当 i=P−1时,gi≡1(modP) i = P − 1 时 , g i ≡ 1 ( mod P ) i=P-1时,g^i\equiv 1(\mod P)(如果存在两个同余的,指数相减一下就有一个同余1的了)
再化简一下,如果 ∃i≠P−1s.t.ai≡1(modP) ∃ i ≠ P − 1 s . t . a i ≡ 1 ( mod P ) \exists i \not= P-1 s.t.a^i\equiv 1(\mod P)
则 ∃i|(P−1)s.t.ai≡1(modP) ∃ i | ( P − 1 ) s . t . a i ≡ 1 ( mod P ) \exists i|(P-1)s.t.a^i \equiv 1(\mod P)
证明的方法就是辗转相处一下得到 agcd(P−1,i)≡1(modP) a g c d ( P − 1 , i ) ≡ 1 ( mod P ) a^{gcd(P-1,i)} \equiv 1(\mod P)
所以先筛因数,然后暴搜即可,复杂度 O(P34) O ( P 3 4 ) O(P^{\frac{3}{4}})。

代码

int getG(int n) {int top = 0;for(int i = 2;i < n - 1; ++i) if(!((n - 1) % i)) q[++top] = i;for(int i = 2, j; ; ++i) {for(j = 1;j <= top; ++j) if(pow(i, q[j], n) == 1) break;if(j == top + 1) return i;}
}

例题分析

很抱歉,这道并不是裸题。
这是神题。
没错这是一道dp题
设f[i][j]为第i数个乘积为j
那么有
F[i,j]=∑F[i−1][k] F [ i , j ] = ∑ F [ i − 1 ] [ k ] F[i,j]=\sum F[i - 1][k]
其中k满足 ∃i∈Ss.t.k∗i≡j(modP) ∃ i ∈ S s . t . k ∗ i ≡ j ( mod P ) \exists i \in S s.t. k*i \equiv j (\mod P)
N太大了,于是我们用矩阵优化。
可是怎么转移?我们换一种写法
F[i,j∗kmodm]=∑F[i][j]C[k] F [ i , j ∗ k mod m ] = ∑ F [ i ] [ j ] C [ k ] F[i,j*k\mod m]=\sum F[i][j]C[k]
其中 C[k]=[k∈S] C [ k ] = [ k ∈ S ] C[k]=[k \in S]
仍然很棘手。(所以是神题嘛)
注意到m是素数于是乎有一个玄学的变换
我们找到m的原根g,然后对m以内的所有数进行映射。
ind[i]=j表示 gj≡i(modm) g j ≡ i ( mod m ) g^j \equiv i (\mod m)
有什么用,我们突然发现,本来是以乘积形式存在的 j∗kmodm j ∗ k mod m j*k \mod m在 Zm Z m Z_m意义下突然通过原根变成了加和形式
j∗kmodm=ind[j]+ind[k]mod(m−1) j ∗ k mod m = i n d [ j ] + i n d [ k ] mod ( m − 1 ) j*k\mod m=ind[j]+ind[k] \mod (m-1)
很神奇有木有!
这个时候重新观察式子
F[i,j+kmod(m−1)]=∑F[i][j]C[k] F [ i , j + k mod ( m − 1 ) ] = ∑ F [ i ] [ j ] C [ k ] F[i,j+k\mod (m-1)]=\sum F[i][j]C[k]
这不是NTT么?
NTT变换加速卷积即可
所以说我们发现了原根的另一个用途,就是可以把 Zm Z m Z_m意义下的乘积运算通过原根幂的带换变成乘积的形式。总复杂度 O(mlogmlogn) O ( m l o g m l o g n ) O(mlogmlogn)
呼呼,终于搞完啦!

代码

/**************************************************************Problem: 3992User: 2014lvzelongLanguage: C++Result: AcceptedTime:3256 msMemory:1676 kb
****************************************************************/#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const double pi = acos(-1);
const int N = 16384, P = 1004535809, K = 13;
int R[N], a[N], b[N], g[K + 1], ng[K + 1], ind[N], q[N], inv[N + 1];
int read() {char ch = getchar(); int x = 0;while(ch < '0' || ch > '9') ch = getchar();for(;ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) - '0' + ch;return x;
}
int pow(int a, int k, int P) {int b = 1; for(; k; a = (1LL * a * a) % P, k >>= 1) if(k & 1) b = (1LL * b * a) % P;return b;
}
void NTT(int *F, int n, int f) {for(int i = 0;i < n; ++i) if(i < R[i]) swap(F[i], F[R[i]]);for(int d = 0;(1 << d) < n; ++d) {int wn = ~f ? g[d] : ng[d], m = 1 << d, m2 = m << 1; for(int j = 0;j < n; j += m2) {for(int w = 1, l = 0;l < m; ++l , w = 1LL * w * wn % P) {int &x = F[j + l], &y = F[j + l + m], t = 1LL * w * y % P;y = x - t; if(y < 0) y += P;x = x + t; if(x >= P) x -= P;}}}if(!(~f)) for(int i = 0;i < n; ++i) F[i] = 1LL * F[i] * inv[n] % P;
}void Mul(int *A, int *B, int n, int m) {for(int i = 0;i < n; ++i) A[i] = 1LL * A[i] * B[i] % P;NTT(A, n, -1);for(int i = m;i < n; ++i) A[i % m] = (A[i % m] + A[i]) % P, A[i] = 0;
}void mpower(int n, int k, int m) {for(int i = 0;i < n; ++i) b[i] = a[i];for(--k; k; k >>= 1) {NTT(a, n, 1);if(k & 1) NTT(b, n, 1), Mul(b, a, n, m);Mul(a, a, n, m);}
}int getG(int n) {int top = 0;for(int i = 2;i < n - 1; ++i) if(!((n - 1) % i)) q[++top] = i;for(int i = 2, j; ; ++i) {for(j = 1;j <= top; ++j) if(pow(i, q[j], n) == 1) break;if(j == top + 1) return i;}
}int main() {int i, j, G, len, L;for(G = 3, g[K] = pow(G, (P - 1) / N, P), ng[K] = pow(g[K], P - 2, P), i = K - 1; ~i; --i) g[i] = 1LL * g[i + 1] * g[i + 1] % P, ng[i] = 1LL * ng[i + 1] * ng[i + 1] % P;for(inv[1] = 1, i = 2; i <= N; ++i) inv[i] = 1LL * (P - inv[P % i]) * (P / i) % P;int n = read(), m = read(), x = read(), S = read();for(G = getG(m), i = 0, j = 1; i < m - 1; ++i, j = (j * G) % m) ind[j] = i; //r^i=j(mod m)while(S--) {i = read(); if(i) a[ind[i]] = 1;}for(len = 1, L = 0, --m; len < m << 1; len <<= 1, ++L) ;for(int i = 0;i < len; ++i) R[i] = (R[i >> 1] >> 1) | ((i & 1) << L - 1);mpower(len, n, m);printf("%d\n", b[ind[x]]);return 0;
}

算法学习FFT系列(2):快速数论变换NTT bzoj3992: [SDOI2015]序列统计例题详解相关推荐

  1. Codeforces-1473-G. Tiles (范德蒙德卷积+快速数论变换NTT)

    题目 传送门 思路 这是题解的思路:传送门 我就再具体写写我对题解的几点理解吧. 首先,解决这个递推式的问题: ansi+1,j=∑k=1mCa+bb−k+jansi,kans_{i+1,j}=\su ...

  2. [快速数论变换 NTT]

    先粘一个模板.这是求高精度乘法的 #include <bits/stdc++.h> #define maxn 1010 using namespace std;char s[maxn];t ...

  3. 数学速算法_新初一】七年级上册数学几何图形初步知识点梳理+例题详解!

    几何图形初步知识网络:知识点梳理背诵1. 我们把实物中抽象的各种图形统称为几何图形. 2.有些几何图形(如长方体.正方体.圆柱.圆锥.球等)的各部分不都在同一平面内,它们是立体图形. 3.有些几何图形 ...

  4. 【学习笔记】超简单的快速数论变换(NTT)(FFT的优化)(含全套证明)

    整理的算法模板合集: ACM模板 目录 一.前置知识 二.快速数论变换(NTT) 三.NTT证明(和FFT的关系) 四.NTT模板 数组形式的实现 vector形式的实现 点我看多项式全家桶(●^◡_ ...

  5. 比FFT还容易明白的NTT(快速数论变换)

    NTT相关 一种快速数论变换算法,这种算法是以数论为基础,对样本点为的数论变换,按时间抽取的方法,得到一组等价的迭代方程,有效高速简化了方程中的计算公式·与直接计算相比,大大减少了运算次数.(见快速傅 ...

  6. 多项式算法2:NTT(快速数论变换)

    多项式算法2:NTT(快速数论变换) 前言 前置知识 正文 前言 算法简介 上次的FFT大量使用浮点数,有精度不好控制的问题,使用可以保证精度的方法又容易超时.这里NTT是在取模的一个前提下找到一个新 ...

  7. FFT 快速傅里叶变换 NTT 快速数论变换

    SDNU 1531 a*b III (FFT模板) Description 计算a乘b,多组输入(50组以内). Input 输入a b,数据范围0 <= a,b <= 10^100000 ...

  8. python蝴蝶代码_快速数论变换(NTT)及蝴蝶操作构造详解

    快速数论变换 本文不会从头开始介绍NTT算法,所以需要先了解FFT:永远在你身后:快速傅里叶变换(FFT)求解多项式乘法​zhuanlan.zhihu.com 除此之外,先简单的铺垫一些数论的概念同余 ...

  9. NTT(快速数论变换)模板

    NTT好文 NTT快速数论变换,在acm中用于解决一类多项式卷积问题. 多项式A和多项式B求卷积后的多项式为C. 显然,如果A是n次多项式,B有m次多项式,这两个多项式都不缺项的话,乘积应为n+m次多 ...

最新文章

  1. C4D+ PS打造城市场景 Create a Cityscape with Cinema 4D + Photoshop
  2. python拼音怎么写-python: 拼音处理模块
  3. Eclipse中server启动超时的解决方法
  4. Java中Volatile的理解
  5. 关于Linux下的umask
  6. react div 事件优先级_React 架构的演变 更新机制
  7. 【老军医方】在脱发过程中遇到的各种疑难杂症
  8. 洛谷——P2393 yyy loves Maths II
  9. Eclipse 中侧边栏、控制台、Server打不开怎么办?
  10. Shell 脚本 ssh免密码 登录 远程服务器 sshpass用法示例
  11. GDB 01 -- 调试信息与调试原理
  12. 忘记root密码,怎么办
  13. java音频下载_java 实现网易云音乐下载和播放
  14. 计算机添加usb网络打印机,方便实用!教您如何简单地将USB打印机更改为无线打印机!...
  15. intelssd在linux固件升级,Intel固件升级教程修复320系列SSD 8M丢数据问题
  16. 如何通过Matlab调用Aspen?
  17. 正大国际琪貨纯手召:期货交易中的五大忌
  18. (CRON) info (No MTA installed, discarding output)” error in the syslog
  19. SQL数据库完美恢复 SQL数据库损坏修复
  20. Web:flex模拟移动商城首页页面布局/grid布局的相关属性

热门文章

  1. 解决React中路由跳转报错:Cannot read property ‘push’ of undefined
  2. Linux命令安装mysql(超详细)
  3. 三星android6.0sd卡,三星 android 手机内置sd卡空间不足,怎么处理?
  4. 高速数据采集卡的分类和使用方法
  5. 一个简单的原生js取色效果
  6. 2 ai and machine learning for coders Laurence Moroney 学习笔记(二)chapter2 计算机视觉导论(Introduction to CV))
  7. 如何优雅地在Ubuntu上快速浏览并安装Google字体
  8. 实现车牌识别之二--使用Yolov3进行车牌定位
  9. 发展规划可视化 电网_调度自动化-可视化产品介绍
  10. android Matrix的invert实现