我这种数学一窍不通的菜鸡终于开始学多项式全家桶了……

必须要会的前置技能:FFT(不会?戳我:【知识总结】快速傅里叶变换(FFT)

以下无特殊说明的情况下,多项式的长度指多项式最高次项的次数加\(1\)

一、NTT

跟FFT功能差不多,只是把复数域变成了模域(计算复数系数多项式相乘变成计算在模意义下整数系数多项式相乘)。你看FFT里的单位圆是循环的,模一个质数也是循环的嘛qwq。\(n\)次单位根\(w_n\)怎么搞?看这里:【BZOJ3328】PYXFIB(数学)(内含相关证明。只看与原根和单位根相关的内容即可。)

注意裸的NTT要求模数\(p\)存在原根并且\(p-1\)是\(2\)的若干次幂的倍数(这个幂要大于多项式次数\(n\))。于是通常就会用著名的NTT模数:\(998244353=2^{23}\times 7\times 17+1\)。

节约篇幅,代码先不放了。后面所有代码里都有NTT模板……

二、多项式求逆

对于\(n\)次多项式\(A\),如果有多项式\(B\)满足\(AB\equiv 1 \mod x^{n+1}\),则称\(B\)是\(A\)在模\(x^{n+1}\)意义下的逆元(和整数逆元差不多)。通常采用倍增的方法求逆元。通常都会规定多项式系数在模\(p\)的意义下。

首先,\(A\)在模\(x\)的意义下就只有一个常数项,所以此时的逆元\(B\)也只有一个常数项,就是\(A\)的常数项模\(p\)的逆元。

如果我们知道\(B_0\)是\(A\)在模\(x^{\lceil\frac{n}{2}\rceil}\)意义下的逆元,现在要求\(B\)是\(A\)在模\(x^n\)意义下的逆元。根据题设,显然有:

\[AB=1\mod x^n
\]

很明显,\(AB\)的\(1\)到\(n-1\)次项系数全是\(0\),所以模一个\(x\)的低于\(n\)次幂也一定是\(1\)。所以

\[AB_0=AB=1\mod x^{\lceil\frac{n}{2}\rceil}
\]

那么

\[B-B_0=0\mod x^{\lceil\frac{n}{2}\rceil}
\]

两边和模数同时平方:

\[B^2+B_0^2-2BB_0=0\mod x^n
\]

两边同时乘\(A\),得到(别忘了\(AB=1\mod x^n\)):

\[B+AB_0^2-2B_0=0\mod x^n
\]

然后移项,得到:

\[B=2B_0-AB_0^2\mod x^n
\]

照着这个式子递归算就行了。

由于后面带余除法的代码包含求逆,所以代码同样略去……

三、加减乘除

加减法:直接每项对应相加减。

乘法:这就是NTT的目的啊喂!

除法:如果不是带余除法直接乘逆元。下面着重介绍带余除法。

已知\(n-1\)次多项式\(F\)和\(m-1\)次多项式\(G\),求\(n-m\)次多项式\(Q\)和多项式\(R\)(\(R\)的次数小于\(m-1\)),满足:

\[F(x)=Q(x)G(x)+R(x) \mod x^n
\]

很明显,主要的难点在于式子里有个叫做\(R\)的嘴子(兔崽子Tzz)。如果能把它搞掉该多好……

注意到\(R\)的次数小于\(m-1\),那么我们把它翻转,末尾补\(0\),是不是就可以把它模成\(0\)了?定义\(\mathrm{Tzz}_{A,n}\)表示把\(A\)视作一个长为\(n\)的多项式(高次项补\(0\))后翻转的结果。即\(\mathrm{Tzz}_{A,n}(x)=x^{n-1}A(\frac{1}{x})=\sum\limits_{i=0}^{n-1}a_ix^{n-i-1}\)

给\(F=QG+R\)的每个多项式都代入同一个数,这个多项式也一定是成立的。所以:

\[F(\frac{1}{x})=Q(\frac{1}{x})G(\frac{1}{x})+R(\frac{1}{x})
\]

两边同乘\(x^{n-1}\),得到:

\[x^{n-1}F(\frac{1}{x})=x^{n-m}Q(\frac{1}{x})\cdot x^{m-1}G(\frac{1}{x})+x^{n-1}R(\frac{1}{x})
\]

\[\mathrm{Tzz}_{F,n}=\mathrm{Tzz}_{Q,n-m+1}\mathrm{Tzz}_{G,m}+\mathrm{Tzz}_{R,n}
\]

现在\(\mathrm{Tzz}_{R,n}\)的最高次项是\(n-1\),但是从常数项到\(n-m\)次项全是\(0\)(因为\(R\)的长度最多就是\(m-1\))。所以现在如果模\(n-m+1\),那么\(\mathrm{Tzz}_{R,n}\)就是\(0\)了,而\(\mathrm{Tzz}_{Q,n-m+1}\)因为最高次是\(n-m\)所以不会受到影响。

于是用\(\mathrm{Tzz}_{F,n}\)乘上\(\mathrm{Tzz}_{G,m}\)的逆元就是\(\mathrm{Tzz}_{Q,n-m+1}\),翻回去就能得到\(Q\)。

最后把\(Q\)代进原式,乘一乘减一减就能算出\(R\)。

所以这样为什么是对的?(以下“低次项”指翻转后的前\(n-m\)项,“高次项”指翻转后的后\(m\)项)首先在模\(x^{n-m+1}\)意义下肯定能保证低次项是对的(即\(\mathrm{Tzz}{F,n}\)与\(\mathrm{Tzz}_{G,m}\mathrm{Tzz}_{Q,n-m+1}\)的前\(n-m\)项相等)。至于高次项,反正有\(\mathrm{Tzz}_{R,n}\)来补锅,所以即使不对也没关系。

完结撒花。

下一篇:【知识总结】多项式全家桶(二)(ln和exp)

代码:洛谷4512

注意NTT的数组一定要保证多余的元素全部是\(0\)。

代码开头的#undef是防机惨护身符。

(我脑子有病啊求原根全是手写的……

  1. #include <cstdio>
  2. #include <algorithm>
  3. #include <cstring>
  4. #include <cctype>
  5. #undef i
  6. #undef j
  7. #undef k
  8. #undef min
  9. #undef max
  10. #undef swap
  11. #undef sort
  12. #undef for
  13. #undef while
  14. #undef if
  15. #undef true
  16. #undef false
  17. #undef printf
  18. #undef scanf
  19. #undef getchar
  20. #undef putchar
  21. #define _ 0
  22. using namespace std;
  23. namespace zyt
  24. {
  25. template<typename T>
  26. inline bool read(T &x)
  27. {
  28. char c;
  29. bool f = false;
  30. x = 0;
  31. do
  32. c = getchar();
  33. while (c != EOF && c != '-' && !isdigit(c));
  34. if (c == EOF)
  35. return false;
  36. if (c == '-')
  37. f = true, c = getchar();
  38. do
  39. x = x * 10 + c - '0', c = getchar();
  40. while (isdigit(c));
  41. if (f)
  42. x = -x;
  43. return true;
  44. }
  45. template<typename T>
  46. inline void write(T x)
  47. {
  48. static char buf[20];
  49. char *pos = buf;
  50. if (x < 0)
  51. putchar('-'), x = -x;
  52. do
  53. *pos++ = x % 10 + '0';
  54. while (x /= 10);
  55. while (pos > buf)
  56. putchar(*--pos);
  57. }
  58. typedef long long ll;
  59. const int N = 1e5 + 10, LEN = (N << 2), p = 998244353;
  60. namespace Polynomial
  61. {
  62. inline int power(int a, int b)
  63. {
  64. a %= p, b %= p - 1;
  65. int ans = 1;
  66. while (b)
  67. {
  68. if (b & 1)
  69. ans = (ll)ans * a % p;
  70. a = (ll)a * a % p;
  71. b >>= 1;
  72. }
  73. return ans;
  74. }
  75. inline int inv(const int a)
  76. {
  77. return power(a, p - 2);
  78. }
  79. namespace Primitive_Root
  80. {
  81. pair<int, int> prime[20];
  82. int cnt;
  83. void get_prime(int n)
  84. {
  85. cnt = 0;
  86. for (int i = 2; i * i <= n; i++)
  87. {
  88. if (n % i == 0)
  89. prime[cnt++] = make_pair(i, 0);
  90. while (n % i == 0)
  91. ++prime[cnt - 1].second, n /= i;
  92. }
  93. }
  94. int get_g(const int n)
  95. {
  96. get_prime(n - 1);
  97. for (int i = 2; i < n; i++)
  98. {
  99. bool flag = true;
  100. for (int j = 0; j < cnt && flag; j++)
  101. flag &= (power(i, (n - 1) / prime[j].first) != 1);
  102. if (flag)
  103. return i;
  104. }
  105. return -1;
  106. }
  107. }
  108. int omega[LEN], winv[LEN], rev[LEN];
  109. void init(const int n, const int lg2)
  110. {
  111. static int g = 0;
  112. if (!g)
  113. g = Primitive_Root::get_g(p);
  114. int w = power(g, (p - 1) / n), wi = inv(w);
  115. omega[0] = winv[0] = 1;
  116. for (int i = 1; i < n; i++)
  117. {
  118. omega[i] = (ll)omega[i - 1] * w % p;
  119. winv[i] = (ll)winv[i - 1] * wi % p;
  120. }
  121. for (int i = 0; i < n; i++)
  122. rev[i] = ((rev[i >> 1] >> 1) | ((i & 1) << (lg2 - 1)));
  123. }
  124. void ntt(int *a, const int *w, const int n)
  125. {
  126. for (int i = 0; i < n; i++)
  127. if (i < rev[i])
  128. swap(a[i], a[rev[i]]);
  129. for (int l = 1; l < n; l <<= 1)
  130. for (int i = 0; i < n; i += (l << 1))
  131. for (int k = 0; k < l; k++)
  132. {
  133. int tmp = (a[i + k] - (ll)w[n / (l << 1) * k] * a[i + l + k] % p + p) % p;
  134. a[i + k] = (a[i + k] + (ll)w[n / (l << 1) * k] * a[i + l + k] % p) % p;
  135. a[i + l + k] = tmp;
  136. }
  137. }
  138. void reverse(int *a, const int n)
  139. {
  140. static int tmp[LEN];
  141. memcpy(tmp, a, sizeof(int[n]));
  142. for (int i = 0; i < n; i++)
  143. a[i] = tmp[n - i - 1];
  144. }
  145. inline void plus(const int *a, const int *b, int *c, const int n)
  146. {
  147. for (int i = 0; i < n; i++)
  148. c[i] = (a[i] + b[i]) % p;
  149. }
  150. inline void minus(const int *a, const int *b, int *c, const int n)
  151. {
  152. for (int i = 0; i < n; i++)
  153. c[i] = (a[i] - b[i] + p) % p;
  154. }
  155. void _inv(const int *a, int *b, const int n)
  156. {
  157. if (n == 1)
  158. b[0] = inv(a[0]);
  159. else
  160. {
  161. static int tmp[LEN];
  162. _inv(a, b, (n + 1) >> 1);
  163. int m = 1, lg2 = 0;
  164. while (m < (n << 1) - 1)
  165. m <<= 1, ++lg2;
  166. memcpy(tmp, a, sizeof(int[n]));
  167. memset(tmp + n, 0, sizeof(int[m - n]));
  168. memset(b + ((n + 1) >> 1), 0, sizeof(int[m - ((n + 1) >> 1)]));
  169. init(m, lg2);
  170. ntt(tmp, omega, m);
  171. ntt(b, omega, m);
  172. for (int i = 0; i < m; i++)
  173. b[i] = (b[i] * 2LL % p - (ll)tmp[i] * b[i] % p * b[i] % p + p) % p;
  174. ntt(b, winv, m);
  175. int invm = inv(m);
  176. for (int i = 0; i < m; i++)
  177. b[i] = (ll)b[i] * invm % p;
  178. memset(b + n, 0, sizeof(int[m - n]));
  179. }
  180. }
  181. void inv(const int *a, int *b, const int n)
  182. {
  183. static int tmp[LEN];
  184. memcpy(tmp, a, sizeof(int[n]));
  185. _inv(tmp, b, n);
  186. }
  187. void mul(const int *a, const int *b, int *c, const int n)
  188. {
  189. int m = 1, lg2 = 0;
  190. while (m < (n << 1))
  191. m <<= 1, ++lg2;
  192. static int x[LEN], y[LEN];
  193. memcpy(x, a, sizeof(int[n]));
  194. memset(x + n, 0, sizeof(int[m - n]));
  195. memcpy(y, b, sizeof(int[n]));
  196. memset(y + n, 0, sizeof(int[m - n]));
  197. init(m, lg2);
  198. ntt(x, omega, m);
  199. ntt(y, omega, m);
  200. for (int i = 0; i < m; i++)
  201. x[i] = (ll)x[i] * y[i] % p;
  202. ntt(x, winv, m);
  203. int invm = inv(m);
  204. for (int i = 0; i < m; i++)
  205. x[i] = (ll)x[i] * invm % p;
  206. memcpy(c, x, sizeof(int[n]));
  207. }
  208. void div(const int *_F, const int *_G, int *_Q, int *_R, const int n, const int m)
  209. {
  210. static int F[LEN], G[LEN], invG[LEN], Q[LEN], R[LEN];
  211. memcpy(F, _F, sizeof(int[n]));
  212. memcpy(G, _G, sizeof(int[m]));
  213. reverse(F, n), reverse(G, m);
  214. if (m < n - m + 1)
  215. memset(G + m, 0, sizeof(int[n - m + 1 - m]));
  216. inv(G, invG, n - m + 1);
  217. mul(F, invG, Q, n - m + 1);
  218. reverse(F, n), reverse(G, m), reverse(Q, n - m + 1);
  219. mul(G, Q, G, n);
  220. minus(F, G, R, n);
  221. memcpy(_Q, Q, sizeof(int[n - m + 1]));
  222. memcpy(_R, R, sizeof(int[m]));
  223. }
  224. }
  225. int F[LEN], G[LEN], Q[LEN], R[LEN];
  226. int work()
  227. {
  228. int n, m;
  229. read(n), read(m);
  230. ++n, ++m;
  231. for (int i = 0; i < n; i++)
  232. read(F[i]);
  233. for (int i = 0; i < m; i++)
  234. read(G[i]);
  235. Polynomial::div(F, G, Q, R, n, m);
  236. for (int i = 0; i < n - m + 1; i++)
  237. write(Q[i]), putchar(' ');
  238. putchar('\n');
  239. for (int i = 0; i < m - 1; i++)
  240. write(R[i]), putchar(' ');
  241. return (0^_^0);
  242. }
  243. }
  244. int main()
  245. {
  246. return zyt::work();
  247. }

【知识总结】多项式全家桶(一)(NTT、加减乘除和求逆)的更多相关文章

  1. 【知识总结】多项式全家桶(三)(任意模数NTT)

    经过两个月的咕咕,"多项式全家桶" 系列终于迎来了第三期--(雾) 上一篇:[知识总结]多项式全家桶(二)(ln和exp) 先膜拜(伏地膜)大恐龙的博客:任意模数 NTT (在页面 ...

  2. 【知识总结】多项式全家桶(二)(ln和exp)

    上一篇:[知识总结]多项式全家桶(一)(NTT.加减乘除和求逆) 一.对数函数\(\ln(A)\) 求一个多项式\(B(x)\),满足\(B(x)=\ln(A(x))\). 这里需要一些最基本的微积分 ...

  3. [模板]多项式全家桶小记(求逆,开根,ln,exp)

    前言 这里的全家桶目前只包括了\(ln,exp,sqrt\).还有一些类似于带余数模,快速幂之类用的比较少的有时间再更,\(NTT\)这种前置知识这里不多说. 还有一些基本的导数和微积分内容要了解,建 ...

  4. Solution -「LOJ #150」挑战多项式 ||「模板」多项式全家桶

    \(\mathcal{Description}\)   Link.   给定 \(n\) 次多项式 \(F(x)\),在模 \(998244353\) 意义下求 \[G(x)\equiv\left\{ ...

  5. loj#6363. 「地底蔷薇」(拉格朗日反演+多项式全家桶)

    题面 传送门 题解 肝了一个下午--我老是忘了拉格朗日反演计算的时候多项式要除以一个\(x\)--结果看它推倒简直一脸懵逼-- 做这题首先你得知道拉格朗日反演是个什么东西->这里 请坐稳,接下来 ...

  6. bzoj3684: 大朋友和多叉树(拉格朗日反演+多项式全家桶)

    题面 传送门 题解 首先你得知道什么是拉格朗日反演->这里 我们列出树的个数的生成函数 \[T(x)=x+\prod_{i\in D}T^i(x)\] \[T(x)-\prod_{i\in D} ...

  7. [模板] 多项式: 乘法/求逆/分治fft/微积分/ln/exp/幂

    多项式 代码 const int nsz=(int)4e5+50; const ll nmod=998244353,g=3,ginv=332748118ll; //basic math ll qp(l ...

  8. IP 基础知识全家桶,45 张图一套带走

    前言 前段时间,有读者希望我写一篇关于 IP 分类地址.子网划分等的文章,他反馈常常混淆,摸不着头脑. 那么,说来就来!而且要盘就盘全一点,顺便挑战下小林的图解功力,所以就来个 IP 基础知识全家桶. ...

  9. Vue全家桶之一Vue(基础知识篇)

    全家桶:Vue本身.状态管理.路由.   异步组件:     

随机推荐

  1. svn汉化包安装无效的解决办法

    下载svn汉化包要和对应的svn客户端版本对应,否则安装无效, 在安装前要想将svn安装目录下的languages目录下的文件全部删除 还有一点要注意的是 汉化包安装要放在svn安装目录下进行安装,它 ...

  2. this与const

    在普通非const成员函数中,this是const指针,而在const成员函数中,this是const对象的const指针. class Foo { Foo& get_self1(void) ...

  3. 腾讯云,体验域名注册解析与SSL证书

    体验域名注册解析与SSL证书 购买域名 任务时间:30min ~ 60min 在腾讯云上购买域名 首先需要在腾讯云上购买域名, 点击以下链接可以观看购买操作的指引 如何在腾讯云上购买域名 域名解析 域 ...

  4. PAT 1143 Lowest Common Ancestor

    The lowest common ancestor (LCA) of two nodes U and V in a tree is the deepest node that has both U ...

  5. kafka 在阿里云部署

    https://blog.csdn.net/chenyulancn/article/details/79499401 https://www.cnblogs.com/yangtianle/p/8761 ...

  6. Android之设置拖拽监听

    以EditText为例: username.setOnDragListener(new OnDragListener() { @Overridepublic boolean onDrag(View v ...

  7. ckeditor 设置含有html标签的值

    ckeditor 设置含有html标签的值 需要使用ajax请求拿到那个字符串,然后用editor.setData(text);

  8. ubuntu update时发生错误

    The following packages have been kept back解决方案Ubuntu和Debian下更新软件包,在运行 sudo apt-get upgrade 有时会看到如下提示 ...

  9. Analyze提示:Value stored to &quot;***&quot;is never read

    text这个变量没有被使用,在当前类中搜索'text'这个变量发现仅仅是被赋值并没有被使用. 提示意思是:删除或者凝视这行代码;

  10. php 把一个数组分成有n个元素的二维数组的算法

    一.第一种解法 <?php //把一个数组分成几个数组 //$arr 是数组 //$num 是数组的个数 function partition($arr,$num){ //数组的个数 $list ...