首页 > 其他分享 >多项式全家桶

多项式全家桶

时间:2023-08-01 20:11:16浏览次数:29  
标签:frac 多项式 全家 Complex const include omega

前言

多项式乱七八糟的公式和做法实在是太多了,有点遭不住,写一个学习笔记,记录一下多项式的各种奇奇怪怪的模板。

多项式乘法

系数表示法

即用这个多项式的每一项系数来表示这个多项式。

对于一个 \(n−1\) 次 \(n\) 项多项式:

\[f(x)=\sum_{i=0}^{n−1}a_ix_i \]

用系数表示法为:\(f(x)=\{a_0,a_1 \dots a_i \dots a_{n−1}\}\)

点值表示法

所以,对于一个 \(n−1\) 次多项式,我们可以用 \(n\) 个点值来表示这个多项式。

即, \(f(x)=\{(x_0,f(x_0)),(x_1,f(x_1)) \dots (x_i,f(x_i)) \dots (x_{n−1},f(x_{n−1}))\}\)

点值表示法的重要优势在于,我们可以通过简单的算术运算来实现多项式的运算,举个例子:

\[f(x) · g(x)=\{(x_0,f(x_0)·g(x_0)),(x_1,f(x_1)·g(x_1)) \dots (x_i,f(x_i)·g(x_i)) \dots (x_{n−1},f(x_{n−1})·g(x_{n-1}))\} \]

单位根

将单位圆上的点所代表的复数叫作单位根,用 \(\omega^k_n\) 表示,\(n\) 表示圆分成的份数,且通常是 \(2\) 的次幂,\(k\) 表示逆时针数第 \(k\) 个点。

单位根有如下几个性质:

\[\omega^k_n = \cos{\frac {k} {n}} + \sin{\frac{k}{n}}\mathrm{i} \]

\[(\omega^1_n)^k = \omega^k_n \]

\[\omega^{2k}_{2n} = \omega^{k}_{n} \]

\[\omega^{k + \frac{2}{n}}_{n} = - \omega^{k}_{n} \]

FFT

\[\begin {alignedat}{3} A(x) & = \sum_{i=1}^{n-1}a_ix^i \\ & = (a_0 + a_2x^2 \dots a_{n-2}x^{n-2}) + (a_1x + a_3x^3 \dots a_{n-1}x^{n-1}) \\ & = (a_0 + a_2x^2 \dots a_{n-2}x^{n-2}) + x(a_1 + a_3x^2 \dots a_{n-1}x^{n-2}) \\ & = A_1(x^2) + xA_2(x^2) \\ \end {alignedat}\]

设\(k < \frac{n}{2}\)然后将 \(\omega^k_n\) 带入上式, 则有,

\[\begin {alignedat}{3} A_1(x^2) + xA_2(x^2) & = A_1((\omega^{k}_{n})^2) + xA_2((\omega^{k}_{n})^2)\\ & = A_1(\omega^{2k}_{n}) + \omega^{k}_{n}A_2(\omega^{2k}_{n}) \\ \end {alignedat}\]

然后我们再将 \(\omega^{k + \frac{n}{2}}_n\) 带入上式, 则有,

\[\begin {alignedat}{3} A_1(x^2) + xA_2(x^2) & = A_1((\omega^{k + \frac{n}{2}}_n)^2) + xA_2((\omega^{k + \frac{n}{2}}_n)^2)\\ & = A_1(\omega^{2k + n}_n) + \omega^{k + \frac{n}{2}}_nA_2(\omega^{2k + n}_n) \\ & = A_1(\omega^{2k}_n) - \omega^{k}_nA_2(\omega^{2k}_n) \\ \end {alignedat}\]

因为有以上的性质存在,我们只需要求出 \(A_1(\omega^{k}_{\frac{n}{2}})\) 和 \(A_2(\omega^{k}_{\frac{n}{2}})\) 的值,就可以求出 \(A (\omega^{k}_n)\) 和 \(A (\omega^{k + \frac{n}{2}}_n)\) 的值。

因此可以倍增的将系数表达式转化成点值表达式,所需要的时间复杂度为 \(O(n\log{n})\)

IFFT

有这样一个结论,证明略。

一个多项式在分治的过程中乘上单位根的共轭复数,分治完的每一项除以 \(n\) 即为原多项式的每一项系数。

因此我们只需要在 FFT 中 \(\omega\) 的虚部乘上一个复数, 然后最终结果除以 \(n\) 即可

迭代优化

倍增过程中迭代的效率太差了,可以发现,分治后的位置,是其下标二进制翻转以后的位置,我们可以先预处理好它最后到达的位置,在 FFT 之前交换即可。

Code

FFT实现代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;

typedef long long lld;

const int N = 1e6 + 50;
const double PI = acos (-1.0);

struct Complex {
	double x, y;
	Complex (register double xx = 0, register double yy = 0) {
		x = xx, y = yy;
	}
	inline Complex operator + (const Complex &a) const {
		return Complex (a.x + x, a.y + y);
	}
	inline Complex operator - (const Complex &a) const {
		return Complex (x - a.x, y - a.y);
	}
	inline Complex operator * (const Complex &a) const {
		return Complex (x * a.x - y * a.y, x * a.y + y * a.x);
	}
} a[N << 2], b[N << 2];

inline int read () {
	register int x = 0, w = 1;
	register char ch = getchar ();
	for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
	for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
	return x * w;
}

int n, m;

int limit = 1;
int l, r[N << 2];
inline void FFT (register Complex * A, register int type) {
	for (register int i = 0; i < limit; i ++)
		if (i < r[i])  swap (A[i], A[r[i]]);
	for (register int mid = 1; mid < limit; mid <<= 1) {
		register Complex Wn (cos (PI / mid), type * sin (PI / mid));
		for (register int R = mid << 1, j = 0; j < limit; j += R) {
			register Complex w (1, 0);
			for (register int k = 0; k < mid; k ++, w = w * Wn) {
				register Complex x = A[j + k], y = w * A[j + mid + k];
				A[j + k] = x + y;
				A[mid + j + k] = x - y;
			}
		}
	}
}

int main () {
	n = read(), m = read();
	for (register int i = 0; i <= n; i ++)  a[i].x = read();
	for (register int i = 0; i <= m; i ++)  b[i].x = read();
	while (limit <= n + m)  limit <<= 1, l ++;
	for (register int i = 0; i < limit; i ++)  r[i] = (r[i >> 1] >> 1) | ((i & 1) << (l - 1));
	FFT (a, 1);
	FFT (b, 1);
	for (register int i = 0; i <= limit; i ++)  a[i] = a[i] * b[i];
	FFT (a, -1);
	for (register int i = 0; i <= n + m; i ++)  printf ("%d%c", (int)(a[i].x / limit + 0.5), i == limit - 1 ? '\n' : ' ');;
	return 0;	
}

NTT

因为 FFT 涉及到了 double, 精度堪忧, 所以考虑寻找一种不需要使用 double 数据类型可以解决的多项式转化点值表达式方法,那么需要找到一个合适的东西来代替单位根,下面的只涉及到定义,而跟具体内容无关。

设 \(a,p\)为 整数,且 \(\gcd{(a,p)} = 1\) ,
使 \(a^n \equiv 1 \pmod{p}\) 成立的最小正整数 \(n\) 叫做 \(a\) 模 \(p\) 的阶,记作 \(\delta_p(a) = n\)。

设 \(p\) 为正整数, \(a\) 为整数, 若 \(a\) 模 \(p\) 的阶等于 \(\varphi(p)\) ,即 \(\delta_p(a) = \varphi(p)\), 则称 \(a\) 为 \(m\) 的一个原根。

对于一个模数 \(p\) ,设他的原根为 \(g\) ,则有,

\[g^{\varphi(p)} \equiv 1 \pmod{p} \]

若 \(p\) 为质数, 则有 \(g^{p - 1} \equiv 1 \pmod{p}\), 根据单位根的性质, 有

\[(\omega^1_n)^n \equiv g^{p - 1} \equiv 1 \pmod{p} \]

\[\omega^1_n \equiv g^{\frac{p - 1}{n} } \pmod{p} \]

\[\omega^{-1}_n \equiv g^{-\frac{p - 1}{n} } \pmod{p} \]

因此,可以使用原根来代替单位根,从而在取模意义下避过了 double 的精度限制, 从而具有更好的精度表现

NTT实现代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;

typedef long long lld;

const int N = 1e6 + 50;
const int mod = 998244353;

int a[N << 2], b[N << 2];

inline int read () {
	register int x = 0, w = 1;
	register char ch = getchar ();
	for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
	for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
	return x * w;
}

int n, m;

int limit = 1;
int l, r[N << 2];

inline int qpow (register int a, register int b) {
	register int base = 1;
	while (b) {
		if (b & 1)  base = 1ll * base * a % mod;
		a = 1ll * a * a % mod;
		b >>= 1;
	}
	return base;
}

inline void FFT (register int * A, register int type) {
	for (register int i = 0; i < limit; i ++)
		if (i < r[i])  swap (A[i], A[r[i]]);
	for (register int mid = 1; mid < limit; mid <<= 1) {
		register int Gn = qpow (type, (mod - 1) / (mid << 1));
		for (register int R = mid << 1, j = 0; j < limit; j += R) {
			register int g = 1;
			for (register int k = 0; k < mid; k ++, g = 1ll * g * Gn % mod) {
				register int x = A[j + k], y = 1ll * g * A[j + mid + k] % mod;
				A[j + k] = (x + y) % mod;
				A[mid + j + k] = (x - y + mod) % mod;
			}
		}
	}
}

int main () {
	n = read(), m = read();
	for (register int i = 0; i <= n; i ++)  a[i] = read();
	for (register int i = 0; i <= m; i ++)  b[i] = read();
	while (limit <= n + m)  limit <<= 1, l ++;
	for (register int i = 0; i < limit; i ++)  r[i] = (r[i >> 1] >> 1) | ((i & 1) << (l - 1));
	FFT (a, 3);
	FFT (b, 3);
	for (register int i = 0; i <= limit; i ++)  a[i] = 1ll * a[i] * b[i] % mod;
	FFT (a, qpow (3, mod - 2));
	for (register int i = 0; i <= n + m; i ++)  printf ("%lld%c", 1ll * a[i] * qpow (limit, mod - 2) % mod, i == limit - 1 ? '\n' : ' ');;
	return 0;	
}

标签:frac,多项式,全家,Complex,const,include,omega
From: https://www.cnblogs.com/abigjiong/p/17597633.html

相关文章

  • 【数学】简单的多项式技巧汇总
    【数学】简单的多项式技巧汇总下面对一些多项式常见操作进行总结前置芝士快速数论变换NTT约定NTT前对于一定长度的范围处理和rev数组初始化函数为\(getrev()\)。inlinevoidgetrev(intlen){tt=1,tw=0;while(tt<=len)tt<<=1,tw++;rev[0]=0;......
  • 程序员进阶必备,这份Android架构师进阶学习资料全家桶助你提升无忧
    走技术这条路的程序员进阶需要具备什么条件呢?大概总结起来有两点:1.扎实的基础底层功底(四大组件、布局使用、多线程、动画…)2.技术的深度和广度(自定义View、性能优化、Flutter、热修复、插件化…)3.同时,了解和学习常用的开源库也十分重要,常用的开源库主要包括图片加载、网络请求、......
  • 椭球面拟合方法及一般多项式函数拟合拓展
    基于对一般二次曲面拟合效果的不满,特地整理这一篇文章。不加任何限制的一般二次曲面拟合在机器视觉实际应用时会出现很多意外的情况。比如文章《匹配位姿拟合求精方法-兜尼完-博客园(cnblogs.com)》和《9点拟合梯度边缘亚像素方法-兜尼完-博客园(cnblogs.com)》,这两种方......
  • 多项式和生成函数
    多项式概念:对于一个求和$\suma_nx^{n}\(,如果这个式子是**有限项**,则称该式为多项式,记作\)f(x)={\textstyle\sum_{n=0}^{m}}a_nx^{n}\(可列项相加的求和式称为级数。在\)\sum_{n=0}^\inftya_nx^n$中,每项均为非负整数次幂函数乘常数系数,这种形式的级数称为幂级数。乘......
  • 数据结构练习笔记——求解由单链表表示的一元多项式的值
    求解由单链表表示的一元多项式的值【问题描述】一个形如\[a_0x^0+a_1x^1+...+a_nx^n\]的一元多项式含有n+1项,每一项由系数和指数唯一确定,可表示成由系数项和指数项构成的一个二元组(系数,指数),一元多项式则可以表示成二元组的集合{(a0,0),(a1,1),(a2,2)...(an,n)},可看成是数据......
  • 题解 P3803 【模板】多项式乘法(FFT)
    感觉题解区不是写的太高深,就是写的太高深。所以给初中、小学和幼儿园的萌新准备一篇简单易懂的良心题解~前置知识一、多项式的系数表示法和点值表示法。\(A(x)=\sum\limits_{i=0}^{n-1}a_i\cdotx^i\)系数:\((a_0,a_1,a_2...a_{n-2},a_{n-1})\)。点值:\((x_0,y_0),(x_1,y_1)...(......
  • 简单多项式
    前置知识复数\(i^2=-1\)复数为\(z=a+bi\)\(a+bi,a-bi\)互为共轭复数一个复数对应复平面上的任意一个点,一个复数与复平面上\((a,b)\)一一对应。复数与向量之间的运算有相似的地方,假设复数到复平面原点的距离为\(r\),那么复数也可以用\((r\cos\alpha,r\sin\alp......
  • 使用MASA全家桶从零开始搭建IoT平台(六)使用规则引擎实现告警通知
    目录前言方案实施流程安装Node-RED配置一个告警处理流程编写代码测试总结前言数据的挑战:物联网的发展带来了海量的数据。这些数据来源多样,格式不一,处理起来十分复杂。同时,物联网中的设备数量庞大,需要设备间进行高效的协同和管理,这也对数据处理提出了更高的要求。如何从这些复......
  • 手把手教你用 NebulaGraph AI 全家桶跑图算法
    前段时间NebulaGraph3.5.0发布,@whitewum吴老师建议我把前段时间NebulaGraph社区里开启的新项目ng_ai公开给大家。所以,就有了这个系列文章,本文是该系列的开篇之作。ng_ai是什么ng_ai的全名是:NebulagraphAISuite,顾名思义,它是在NebulaGraph之上跑算法的Python套......
  • 多项式全家桶
    多项式基本运算这个博客主要用来放一些多项式的运算的板子,大部分都来自于洛谷。多项式乘法NTT求个卷积即可。#include<bits/stdc++.h>usingnamespacestd;typedeflonglongll;constintN=3e6+10,mod=998244353,GG=3,Gi=332748118;llqmi(lla,llk,......