多项式全家桶【长期更新】
多项式
定义(表达式)
定义一个
注意 :
的那一项是常数项。- 易得
- 在OI中,多项式一般在同余条件下讨论
,在下文中会省略 " " 符号。 - 求多项式
,实际上就是把每一个 求出来。
暴力全家桶
加法
给定两个多项式
即: $ H(x) = F(x) + G(x)
乘法
即:
就是合并同类项
复杂度
余数除法
已知F和H,求G和R
即:
就是小学奥数里的大除法。
求导和积分
若:
则:
若:
则:
求逆
F已知求G。
即:
递推求即可。
开根
还是用递推,
特别的,
-
若
,则 的解数,取决于 的平方根数量。(具体参见二次剩余) -
若
,令 ,满足 ,那么若 为奇数,则 无解;若 为偶数,记 ,那么 。
这是因为开跟运算本身不一定有解。
求对数
给定多项式
解:对原等式两边求导,得:
求指数
给定多项式
对原等式两边求导,得:
此式子也可看成求对数的第三步和第四步,把F换成G,把G换成F
求
求三角函数
前置知识:[欧拉公式(
由欧拉公式:
两式相加得
两式相减得
现在我们把三角函数换成了指数函数,用指数的方法来推导三角函数。
快速傅里叶变换
终于进入正题了。
前置内容
-
多项式插值:n + 1 个点值可确定一个 n 次多项式。
很好理解。用待定系数法高斯消元。
系数矩阵满秩,所以不会是无数解。
有如果无解,说明有两行的系数,上面的系数都是下面的系数的k倍。但是对于不同的x,
的集合不会是这种比例关系。 -
复数运算:参见数学书
-
单位根
P.S. 以下为了方便书写,均将 简写为 ,且将 表示为 。定理:任何复系数一元n次多项式方程在复数域上至少有一个根。
大多数数学家都认为这是对的。
推论:任何复系数一元
次多项式方程在复数域上恰好有 个根。设现有一根
。若将原 n 次多项式因式分解,必定存在一项形如
。把这一项去掉,得到一个 n-1 次的多项式。而这个新的多项式必有一根。再把这个根去掉。重复这个操作。因为可以降幂 n 次,所以有 n 个根。单位根定义:
的 个根,记作 。单位根几何意义:建立复数域的坐标系。做单位圆。运算用向量的运算。这里以
为例。用三角函数来表达这些单位根可得:
首先显然有:
,这相当与多转 k 圈。通过化简,
,这个可以看为把和x轴的夹角翻倍。同理(大概是用一些二项式定理和欧拉定理展开后乱搞),
所以一个单位根的
次方都是下面给出一些关于单位根的运算性质 :
-
-
-
次方转下标 -
-
单位根反演
(应该只在IDFT的证明中有用到)
这里用
代表证明:
该式为等比数列,所以:
观察到,
,并且所以上式化简为:
-
FFT
这玩意可以做到
核心思路
根据多项式插值,我们根据
通过优秀的选点(单位根),可以做到
现在我们有两个要做的事情:
- DFT:输入一个
次多项式的系数列,快速得到其在 次单位根处的点值列,也就是求 。 - IDFT:在优秀的时间复杂度内插值出多项式,也就是用
去求 (点值求系数)。
具体做法
先来解决DFT:
带入
带入
所以带入
于是可以递归求解。
复杂度是
可以发现,DFT能做到高效的原理实际上是利用的单位根的运算性质。
再来看IDTF:
根据单位根反演可以推到出这个公式:
证明:
这个公式与DTF相比,左边多一个 n ,右边的 k 变成了 -k,其余一致。
常数优化:非递归FFT
注意到,第 k 个点和第 k + len / 2 个点 由 第 k 个点和第 k + len/2 共同转移来,并且每次len除2。
若执行到了第 k 次,那么把从右往左数第 k 位是 0 的数往前提。
所以对于最后一排,最后一位是 0 的排在前面,如果最后一位都是 0,那么倒数第二排是 0 的排在前面...以此类推。
容易发现,这相当于倒着比较二进制数的大小。
考虑最后一行的初始化:第 i 位就应该为
写成代码是这样的:
for (int i = 0; i < len; ++i) {
rev[i] = rev[i >> 1] >> 1;
if (i & 1) rev[i] |= len >> 1;\\变换最高位从左往右第一位?神秘
}
for (int i = 0; i < len; ++i) if (i < rev[i]) swap(y[i], y[rev[i]]); \\这样才能刚好交换一次
之后就可以从低位向高位递推了。
附上代码:$$
typedef complex<double > com;
const int N=(1e6+6) * 4;
const double PI = acos(-1);
int n,m,rev[N];
com f[N],g[N];
void change(int n) {
int L = log2(n);
for(int i=0;i<n;++i)rev[i] = (rev[i>>1]>>1) | ((i&1) << (L-1));
}
void FFT(com *f, int n,int op) {
change(n)
for(int i=0;i<n;++i) if(rev[i] < i) swap(f[i], f[rev[i] ]);
for(int mid = 1;mid < n;mid <<= 1){
int j = mid << 1;
com nxt(cos(PI / mid), sin(PI / mid) * op);
for(int st = 0;st < n; st += j){
com w(1.0, 0.0);
for(int i = st;i < st + (j>>1); ++i, w *= nxt){
com tmp1 = f[i],tmp2 = w * f[i + mid];
f[i] = tmp1 + tmp2;
f[i + mid] = tmp1 - tmp2;
}
}
}
}
NTT
除法意味着精度误差,所以很多人不喜欢除法,而使用在模数的逆元来替代除法。
FFT用的是单位根的点值,而NTT则运用原根,并且可以避免除法运算,被广泛使用。
原根定义:对于模数
原根有和单位根一样优良的性质。设
=
但是这不代表我们可以直接将他套用进 FFT 中。因为这里必须要求
幸运的是我们发现质数
这使得这个数字充满了美好的寓意
代码(摘自 oi-wiki.org)
void ntt(int *x, int lim, int opt) {
int i, j, k, m, gn, g, tmp;
for (i = 0; i < lim; ++i)
if (r[i] < i) swap(x[i], x[r[i]]);
for (m = 2; m <= lim; m <<= 1) {
k = m >> 1;
gn = qpow(3, (P - 1) / m);
for (i = 0; i < lim; i += m) {
g = 1;
for (j = 0; j < k; ++j, g = 1ll * g * gn % P) {
tmp = 1ll * x[i + j + k] * g % P;
x[i + j + k] = (x[i + j] - tmp + P) % P;
x[i + j] = (x[i + j] + tmp) % P;
}
}
}
if (opt == -1) {
reverse(x + 1, x + lim);
int inv = qpow(lim, P - 2);
for (i = 0; i < lim; ++i) x[i] = 1ll * x[i] * inv % P;
}
}
快速多项式全家桶
加法和乘法和求导积分略过。
前置知识:牛顿迭代
对于一个方程
当我们把
假如一个函数
为了方便表示,设
那么如果我们先预估一个函数
证明:
因为
为了求
因为
求逆
F已知求G。
即:
我们要想办法把这玩意化成可以牛顿迭代的形式:
可以得到
迭代的初值 :
使用牛顿迭代的公式得到 :
我们对这个式子化简(分子分母同乘
时间复杂度
余数除法
已知F和H,求G和R
即:
思路:如果没有 R(x),这个题就用逆元做,所以要想办法避开 R.
用普通的方法很难求,但是观察到,R 的次数比 H 低,于是考虑把函数系数翻转后,发现R的后几项的系数为0,再用取模的方式把 R 干掉。
做法:设
设
容易得知
证明:
复杂度同求逆
开根
转成可以牛顿迭代的形式:
特别的,
-
若
,则 的解数,取决于 的平方根数量。(具体参见二次剩余) -
若
,令 ,满足 ,那么若 为奇数,则 无解;若 为偶数,记 ,那么 。
这是因为开跟运算本身不一定有解。
ln
给定多项式
解:对原等式两边求导,得:
exp
给定多项式
对两边取
求三角函数
现在我们把三角函数换成了指数函数,用指数的方法来推导三角函数。
多项式与分治
给定长为
看作两个多项式相乘,用 FFT
参考资料:上课的课件,大佬的博客。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析