【知识总结】多项式全家桶(三)(任意模数NTT)
经过两个月的咕咕,“多项式全家桶” 系列终于迎来了第三期……(雾)
上一篇:【知识总结】多项式全家桶(二)(ln和exp)
先膜拜(伏地膜)大恐龙的博客:任意模数 NTT
(在页面右侧面板 “您想嘴谁” 中选择 “大恐龙” 就可以在页面左下角戳她哦)
首先务必先学会 NTT (如果不会,请看多项式全家桶(一)),并充分理解中国剩余定理……
之前提到了,普通 NTT 的模数必须是一个质数,且这个质数中必须有一个足够大的 \(2\) 的幂作为因子。然而,毕竟满足这样条件的数不多,如果题目给定的模数不满足这样的条件呢?当然是大骂出题人毒瘤简单来说,就是用三个满足条件的模数 (常用的有 \(469762049\)、\(998244353\) 和 \(1004535809\)。最后将介绍如何背过它们)分别做 NTT ,然后用中国剩余定理(下文缩写作 CRT )合并。因为如果所有系数都不超过 \(10^9\) ,项数是 \(10^5\) ,那么最终每项系数(不取模)最大是 \(10^9\times 10^9 \times 10^5=10^{23}\)。而 CRT 合并后的数是模上述三个质数乘积(大约 \(10^{26}\) 这个数量级)的,所以把 CRT 合并后的答案模题目给定的模数即可。
然而很明显 long long 存不下啊……于是 CRT 的时候有技巧。
很明显 CRT 的时候各项独立,于是每一项分别处理。设这一项答案是 \(x\) ,上述三个质数是 \(p_0\) 、 \(p_1\) 和 \(p_2\) ,满足:
x=a_1 \mod p_1\\
x=a_2 \mod p_2\\
\end{cases}\]
暴力合并是会爆 long long 的,不过可以先合并前两项( \(p_0\times p_1<2^{63}\)),得到(\(\mathrm{inv}(x,y)\) 表示 \(x\) 在模 \(y\) 意义下的逆元):
\]
把右边那一堆设为 \(t_0\) (模仿恐龙的变量名 qwq ),设答案为 \(t_1p_0p_1+t_0\) ,则有:
\]
即:
\]
因为 \(x<p_0p_1p_2\) ,所以 \(t_1<p_2\) ,所以求出来直接就是真正的 \(t_1\) 。然后在模题目给定的模数意义下算 \(t_1p_0p_1+t_0\) 就行了。
下面介绍如何记住那三个模数,记忆力好的可以直接跳过
我是在机房里以被兔崽子揍一顿的代价把这三个数字吼了一上午并且用安徽黄梅戏《天仙配》的曲调唱了一晚上才背过
先看一眼要记住的东西:
469762049、998244353、1004535809
记不住?跟我大声念:
肆陆玖柒陆贰零肆玖!
玖玖捌贰肆肆叁伍叁!
壹零零肆伍叁伍捌零玖!
没记住?再来一遍:
肆陆玖柒陆贰零肆玖!
玖玖捌贰肆肆叁伍叁!
壹零零肆伍叁伍捌零玖!
还没记住?再吼十遍
在页面右侧面板点一下 “运动员进行曲” ,然后跟着唱:
(前奏:当当当当当当当当当当当当当!)
肆陆玖柒陆啊贰零~~肆玖~~
肆陆陆玖柒陆啊贰零肆玖~
壹零零零零肆伍叁伍~捌!零!玖!
玖玖捌贰肆肆肆肆肆叁叁叁~叁伍叁!
(以上重复一遍)
肆陆玖柒陆贰零肆玖~~
玖玖捌贰肆肆叁伍叁~~
壹零零~啊肆伍叁伍捌~零玖
玖玖捌贰肆肆叁伍叁~~
(以上重复一遍)
代码:
注意在模 \(p_0p_1\) 意义下算乘法的时候要用龟速乘防止爆 long long 。这里介绍一种从恐龙那里学来的\(O(1)\) 龟速乘:
inline ll mul(ll a, ll b, const ll p)
{
const static long double eps = 1e-15;
a %= p, b %= p;
return ((a * b - ll((long double)a / p * b + eps) * p) % p + p) % p;
}
原理是利用了 long long 溢出后不是随机数,而只是真实值减去了 \(2^{64}\) ,所以 \(a\) 和 \(\lfloor\frac{a}{b}\rfloor\cdot b\) 之差仍然是 \(a\mod b\)。
下一篇:【知识总结】多项式全家桶(四)(快速幂和开根)
完整代码(洛谷4245):
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cctype>
using namespace std;
namespace zyt
{
template<typename T>
inline bool read(T &x)
{
char c;
bool f = false;
x = 0;
do
c = getchar();
while (c != EOF && c != '-' && !isdigit(c));
if (c == EOF)
return false;
if (c == '-')
f = true, c = getchar();
do
x = x * 10 + c - '0', c = getchar();
while (isdigit(c));
if (f)
x = -x;
return true;
}
template<typename T>
inline void write(T x)
{
static char buf[20];
char *pos = buf;
if (x < 0)
putchar('-'), x = -x;
do
*pos++ = x % 10 + '0';
while (x /= 10);
while (pos > buf)
putchar(*--pos);
}
typedef long long ll;
const int N = 1e5 + 10, LEN = N << 2, p[] = {469762049, 998244353, 1004535809};
int n;
inline ll mul(ll a, ll b, const ll p)
{
const static long double eps = 1e-15;
a %= p, b %= p;
return ((a * b - ll((long double)a / p * b + eps) * p) % p + p) % p;
}
inline ll power(ll a, ll b, const ll p)
{
ll ans = 1;
while (b)
{
if (b & 1)
ans = (ll)ans * a % p;
a = (ll)a * a % p;
b >>= 1;
}
return ans;
}
ll inv(const ll a, const ll p)
{
return power(a % p, p - 2, p);
}
namespace Polynomial
{
int rev[LEN], omega[LEN], winv[LEN], p;
void ntt(int *a, const int *w, const int n)
{
for (int i = 0; i < n; i++)
if (i < rev[i])
swap(a[i], a[rev[i]]);
for (int l = 1; l < n; l <<= 1)
for (int i = 0; i < n; i += (l << 1))
for (int k = 0; k < l; k++)
{
int x = a[i + k], y = (ll)w[n / (l << 1) * k] * a[i + l + k] % p;
a[i + k] = (x + y) % p;
a[i + l + k] = (x - y + p) % p;
}
}
void init(const int n, const int lg2)
{
const static int g = 3;
int w = power(g, (p - 1) / n, p), wi = inv(w, p);
omega[0] = winv[0] = 1;
for (int i = 1; i < n; i++)
{
omega[i] = (ll)omega[i - 1] * w % p;
winv[i] = (ll)winv[i - 1] * wi % p;
}
for (int i = 0; i < n; i++)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (lg2 - 1));
}
void mul(const int *a, const int *b, int *c, const int n, const int m, const int _p)
{
static int x[LEN], y[LEN];
p = _p;
int len = 1, lg2 = 0;
while (len < (n + m - 1))
len <<= 1, ++lg2;
memcpy(x, a, sizeof(int[n]));
memset(x + n, 0, sizeof(int[len - n]));
memcpy(y, b, sizeof(int[m]));
memset(y + m, 0, sizeof(int[len - m]));
init(len, lg2);
ntt(x, omega, len), ntt(y, omega, len);
for (int i = 0; i < len; i++)
x[i] = (ll)x[i] * y[i] % p;
ntt(x, winv, len);
int invlen = inv(len, p);
for (int i = 0; i < len; i++)
c[i] = (ll)x[i] * invlen % p;
}
}
int A[N], B[N], ans[4][LEN];
void CRT(const int n, const int mod)
{
ll p01 = (ll)p[0] * p[1];
ll m0 = mul(p[1], inv(p[1], p[0]), p01), m1 = mul(p[0], inv(p[0], p[1]), p01);
ll inv2 = inv(p01, p[2]);
for (int i = 0; i < n; i++)
{
ll t0 = (mul(ans[0][i], m0, p01) + mul(ans[1][i], m1, p01)) % p01;
ll t1 = (ans[2][i] - t0 % p[2] + p[2]) % p[2] * inv2 % p[2];
ans[3][i] = (t1 % mod * (p01 % mod) % mod + t0) % mod;
}
}
int work()
{
int n, m, P;
read(n), read(m), read(P);
++n, ++m;
for (int i = 0; i < n; i++)
read(A[i]);
for (int i = 0; i < m; i++)
read(B[i]);
for (int i = 0; i < 3; i++)
Polynomial::mul(A, B, ans[i], n, m, p[i]);
CRT(n + m - 1, P);
for (int i = 0; i < n + m - 1; i++)
write(ans[3][i]), putchar(' ');
return 0;
}
}
int main()
{
freopen("4245.in", "r", stdin);
return zyt::work();
}
【知识总结】多项式全家桶(三)(任意模数NTT)的更多相关文章
- 【知识总结】多项式全家桶(二)(ln和exp)
上一篇:[知识总结]多项式全家桶(一)(NTT.加减乘除和求逆) 一.对数函数\(\ln(A)\) 求一个多项式\(B(x)\),满足\(B(x)=\ln(A(x))\). 这里需要一些最基本的微积分 ...
- 【知识总结】多项式全家桶(一)(NTT、加减乘除和求逆)
我这种数学一窍不通的菜鸡终于开始学多项式全家桶了-- 必须要会的前置技能:FFT(不会?戳我:[知识总结]快速傅里叶变换(FFT)) 以下无特殊说明的情况下,多项式的长度指多项式最高次项的次数加\(1 ...
- [模板]多项式全家桶小记(求逆,开根,ln,exp)
前言 这里的全家桶目前只包括了\(ln,exp,sqrt\).还有一些类似于带余数模,快速幂之类用的比较少的有时间再更,\(NTT\)这种前置知识这里不多说. 还有一些基本的导数和微积分内容要了解,建 ...
- BZOJ1042 HAOI2008硬币购物(任意模数NTT+多项式求逆+生成函数/容斥原理+动态规划)
第一眼生成函数.四个等比数列形式的多项式相乘,可以化成四个分式.其中分母部分是固定的,可以多项式求逆预处理出来.而分子部分由于项数很少,询问时2^4算一下贡献就好了.这个思路比较直观.只是常数巨大,以 ...
- [洛谷P4245]【模板】任意模数NTT
题目大意:给你两个多项式$f(x)$和$g(x)$以及一个模数$p(p\leqslant10^9)$,求$f*g\pmod p$ 题解:任意模数$NTT$,最大的数为$p^2\times\max\{n ...
- MTT:任意模数NTT
MTT:任意模数NTT 概述 有时我们用FFT处理的数据很大,而模数可以分解为\(a\cdot 2^k+1\)的形式.次数用FFT精度不够,用NTT又找不到足够大的模数,于是MTT就应运而生了. MT ...
- 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT)
再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT) 目录 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Blueste ...
- 任意模数NTT
任意模数\(NTT\) 众所周知,为了满足单位根的性质,\(NTT\)需要质数模数,而且需要能写成\(a2^{k} + r\)且\(2^k \ge n\) 比较常用的有\(998244353,1004 ...
- 【模板】任意模数NTT
题目描述: luogu 题解: 用$fft$水过(什么$ntt$我不知道). 众所周知,$fft$精度低,$ntt$处理范围小. 所以就有了任意模数ntt神奇$fft$! 意思是这样的.比如我要算$F ...
随机推荐
- hdu 1179最大匹配
#include<stdio.h> #include<string.h> #define N 200 int map[N][N],visit[N],link[N],n,m; i ...
- Python模块:Re模块、附软件开发目录规范
Re模块:(正则表达式) 正则表达式就是字符串的匹配规则 正则表达式在多数编程语言里都有相应的支持,Python里面对应的模块时re 常用的表达式规则:(都需要记住) “ . ” # 默认匹配除 ...
- 2018/3/4 Activiti教程之对于流程的基本操作以及从发起到完成还有相关注意事项(与Springboot整合版)三
写教程实在太累了,,,还浪费时间,Activiti教程就写到这好了,不过最近在玩区块链,到时候写几个区块链方面的教程. 这是一些流程的查询与删除api,删除这块,默认是级联删除(加个false参数,就 ...
- Ext-js使用指南(总结)
一.获取元素(Getting Elements) 1.Ext.get var el = Ext.get('myElementId');//获取元素,等同于document.getElementById ...
- Eclipse-Java代码规范和质量检查插件-PMD
PMD是一个源代码分析器. 它发现常见的编程缺陷,如未使用的变量.空catch块.不必要的对象创建等等. 它支持Java.JavaScript.Salesforce.com Apex.PLSQL.Ap ...
- 我的arcgis培训照片10
来自:http://www.cnblogs.com/gisoracle/p/4297439.html
- vijos - P1447开关灯泡 (大数模板 + 找规律 + 全然数 + python)
P1447开关灯泡 Accepted 标签:CSC WorkGroup III[显示标签] 描写叙述 一个房间里有n盏灯泡.一開始都是熄着的,有1到n个时刻.每一个时刻i,我们会将i的倍数的灯泡改变状 ...
- js中的封装、继承、多态
Javascript是一门解释型的语言,是基于对象的,并不是真正的面向对象的语言,对变量类型的应用也是宽松的,其实它同样可以模拟面向对象的功能: 1 function myfun1(){ 2 ...
- 字符串处理(正则表达式、NSScanner扫描、CoreParse解析器)-b
搜索 在一个字符串中搜索子字符串 最灵活的方法 1 - (NSRange)rangeOfString:(NSString *)aString options:(NSStringCompareOptio ...
- C# 最基本的涉及模式(单例模式) C#种死锁:事务(进程 ID 112)与另一个进程被死锁在 锁 | 通信缓冲区 资源上,并且已被选作死锁牺牲品。请重新运行该事务,解决方案: C#关闭应用程序时如何关闭子线程 C#中 ThreadStart和ParameterizedThreadStart区别
C# 最基本的涉及模式(单例模式) //密封,保证不能继承 public sealed class Xiaohouye { //私有的构造函数,保证外部不能实例化 private ...