【数学】简单的多项式技巧汇总
【数学】简单的多项式技巧汇总
下面对一些多项式常见操作进行总结
前置芝士
快速数论变换NTT
约定NTT前对于一定长度的范围处理和rev数组初始化函数为
inline void getrev(int len)
{
tt = 1,tw = 0;
while(tt <= len) tt <<= 1,tw++;
rev[0] = 0;
for(int i = 1;i < tt;i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (tw - 1));
}
多项式求逆
给定
考虑倍增法,假设已经有一个函数
作差得:
假定F不为0,则有:
平方得:
同时乘上F(x):
递归进行计算即可,边界值为
(代码中的
inline void getinv(int *x,int *y,int len)
{
if(len == 1)
{
y[0] = ksm(x[0],MOD - 2);
return;
}
getinv(x,y,(len + 1) >> 1);
getrev((len << 1) - 1);
for(int i = 0;i < len;i++) c[i] = x[i];
fill(c + len,c + tt,0);
NTT(c,1);NTT(y,1);
for(int i = 0;i < tt;i++) y[i] = (2ll * y[i] % MOD - 1ll * y[i] * y[i] % MOD * c[i] % MOD + MOD) % MOD;
NTT(y,-1);
fill(y + len,y + tt,0);
}
多项式除法/多项式取模
给定
-
为 次, 小于 次。 -
考虑
用这个转化式子:
由于
分别计算即可。
inline void rvs(int *x,int len)
{
for(int i = 0;len - i - 1 > i;i++) swap(x[i],x[len - i - 1]);
}
inline void div(int *x,int len1,int *y,int len2,int *quo,int *rem)
{
for(int i = 0;i < len2;i++) GR[len2 - 1 - i] = y[i];
if(len1 - len2 + 1 <= len2) fill(GR + len1 - len2 + 1,GR + len2,0);
rvs(x,len1);
getinv(GR,c,len1 - len2 + 1);
getrev((len1 + len2) << 1);
for(int i = 0;i < len1;i++) quo[i] = x[i];
fill(quo + len1,quo + tt,0);
NTT(c,1);NTT(quo,1);
for(int i = 0;i < tt;i++) quo[i] = 1ll * quo[i] * c[i] % MOD;
NTT(quo,-1);NTT(c,-1);
fill(quo + len1 - len2 + 1,quo + tt,0);
rvs(quo,len1 - len2 + 1);
rvs(x,len1);
NTT(quo,1);NTT(y,1);
for(int i = 0;i < tt;i++) y[i] = 1ll * y[i] * quo[i] % MOD;
NTT(quo,-1);NTT(y,-1);
fill(quo + len1 - len2 + 1,quo + tt,0);
for(int i = 0;i < tt;i++) rem[i] = (x[i] - y[i] + MOD) % MOD;
fill(rem + len2 - 1,rem + tt,0);
}
多项式ln
事实上多项式求
给定
有定理:多项式
设
(复合函数求导)
发现这个只需要求逆就好了,容易计算,再将函数做不定积分就可以求出答案。
求导方法:
设一个多项式为
不定积分反之。
inline void diff(int *x,int len)
{
for(int i = 0;i < len - 1;i++) x[i] = 1ll * x[i + 1] * (i + 1) % MOD;
x[len - 1] = 0;
}
inline void intg(int *x,int len)//len是多项式现在的长度
{
for(int i = len;i >= 1;i--) x[i] = 1ll * x[i - 1] * inv[i] % MOD;
x[0] = 0;
}
inline void getln(int *x,int *y,int len)
{
fill(y,y + len * 3,0);
fill(b,b + len * 3,0);
for(int i = 0;i < len;i++) y[i] = x[i];
getinv(y,b,len);
diff(y,len);
getrev(len << 1);
NTT(y,1);NTT(b,1);
for(int i = 0;i < tt;i++) y[i] = 1ll * y[i] * b[i] % MOD;
NTT(y,-1);
intg(y,len - 1);
fill(y + len,y + tt,0);
}
多项式exp
首先要从一点说起——牛顿迭代法。
牛顿迭代常用于求一个函数的零点,假定我们有一个曲线:
我们任取一个点,做函数的切线,取其与
假设切点为
由于与
对于多项式来说也一样:
试求
有结论是,这样迭代每次的精度是翻倍的,也就是说假设
考虑将这个代入exp,我们发现:
则:
假设
对这个函数求导:
因为:
在这里,由于已知,我们将
代入:
递归计算即可。
越复杂使用的辅助数组越多,一定要合理分配辅助数组。
inline void getexp(int *x,int *y,int len)
{
if(len == 1)
{
y[0] = 1;
return;
}
getexp(x,y,(len + 1) >> 1);
fill(e + 0,e + len,0);
getln(y,e,len);
for(int i = 0;i < len;i++) e[i] = (x[i] - e[i] + MOD) % MOD;
e[0]++;
fill(e + len,e + tt,0);
getrev(len << 1);
NTT(e,1);NTT(y,1);
for(int i = 0;i < tt;i++) y[i] = 1ll * e[i] * y[i] % MOD;
NTT(y,-1);
fill(y + len,y + tt,0);
}
多项式快速幂
比较简单:
套
有结论:次数
如果
如果
inline void polyksm(int *x,int *y,int pts,int pts2,int len)
{
int pos = 0,org;
while(!x[pos] && pos < len) ++pos;
if(1ll * pts * pos > len) return;
for(int i = 0;i < len - pos;i++) x[i] = x[i + pos];
fill(x + len - pos,x + len,0);
len -= pos;
org = x[0];
for(int i = 0;i < len;i++) x[i] = 1ll * x[i] * ksm(org,MOD - 2) % MOD;
fill(c,c + len * 3,0);
getln(x,c,len);
for(int i = 0;i < len;i++) c[i] = 1ll * c[i] * pts % MOD;
getexp(c,y,len);
for(int i = 0;i < len;i++) y[i] = 1ll * y[i] * ksm(org,pts2) % MOD;
len += pos;
for(int i = len - 1;i >= pos * pts;i--) y[i] = y[i - pos * pts];
fill(y,y + pos * pts,0);
}
(没有包含特判部分)。
多项式开根
求
倍增。
设
递归计算即可,边界值
inline void sqrt(int *x,int *y,int len)
{
if(len == 1)
{
y[0] = getsq(x[0]);
return;
}
sqrt(x,y,(len + 1) >> 1);
fill(c + 0,c + len * 2,0);
getinv(y,c,len);
int iv2 = ksm(2,MOD - 2);
getrev(len << 1);
for(int i = 0;i < len;i++) help[i] = x[i];
fill(help + len,help + tt,0);
NTT(help,1);NTT(c,1);
for(int i = 0;i < tt;i++) help[i] = 1ll * help[i] * c[i] % MOD;
NTT(help,-1);
for(int i = 0;i < len;i++) y[i] = 1ll * (y[i] + help[i]) % MOD * iv2 % MOD;
fill(y + len,y + tt,0);
}
任意模数:三模数NTT
对于任意模数取模,如果
证明:咕。
#include<bits/stdc++.h>
using namespace std;
const int N = 4e5 + 5,MOD[3] = {998244353,1004535809,469762049},g = 3,gi[3] = {332748118,334845270,156587350};
inline int ksm(int base,int pts,int i)
{
int ret = 1;
for(;pts > 0;pts >>= 1,base = 1ll * base * base % MOD[i])
if(pts & 1)
ret = 1ll * ret * base % MOD[i];
return ret;
}
inline void exgcd(__int128 a,__int128 b,__int128 &x,__int128 &y)
{
if(b == 0)
{
x = 1,y = 0;
return;
}
exgcd(b,a % b,x,y);
__int128 t = y;
y = x - (a / b) * y;
x = t;
}
int rev[N],tt,tw,F[N],G[N],R[N],a[N],b[N],ans[4][N],n,m,p;
inline void getrev(int len)
{
tt = 1,tw = 0;
while(tt < len) tt <<= 1,tw++;
for(int i = 1;i < tt;i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (tw - 1));
}
inline void NTT(int *x,int type,int mod)
{
for(int i = 0;i < tt;i++) if(i > rev[i]) swap(x[i],x[rev[i]]);
for(int mid = 1;mid < tt;mid <<= 1)
{
int omega = ksm((type == 1) ? g : gi[mod],(MOD[mod] - 1) / (mid << 1),mod);
for(int i = 0,R = mid << 1;i < tt;i += R)
{
int w = 1;
for(int j = i;j < i + mid;j++,w = 1ll * w * omega % MOD[mod])
{
int X = x[j],Y = 1ll * x[j + mid] * w % MOD[mod];
x[j] = (X + Y) % MOD[mod];
x[j + mid] = (X - Y + MOD[mod]) % MOD[mod];
}
}
}
if(type == -1)
{
int iv = ksm(tt,MOD[mod] - 2,mod);
for(int i = 0;i < tt;i++) x[i] = 1ll * x[i] * iv % MOD[mod];
}
}
inline int crt(int x,int y,int z)
{
__int128 M = MOD[0] * 1ll * MOD[1];
M *= MOD[2];
__int128 iv10,iv20,iv01,iv21,iv02,iv12;
iv10 = ksm(MOD[1],MOD[0] - 2,0);
iv20 = ksm(MOD[2],MOD[0] - 2,0);
iv01 = ksm(MOD[0],MOD[1] - 2,1);
iv21 = ksm(MOD[2],MOD[1] - 2,1);
iv02 = ksm(MOD[0],MOD[2] - 2,2);
iv12 = ksm(MOD[1],MOD[2] - 2,2);
__int128 ret = 0;
ret = (1ll * x * MOD[1] % M * MOD[2] % M * iv10 % M * iv20 % M + 1ll * y * MOD[0] % M * MOD[2] % M * iv01 % M * iv21 % M) % M;
ret = (ret + 1ll * z * MOD[0] % M * MOD[1] % M * iv02 % M * iv12 % M) % M;
return ret % p;
}
int main()
{
cin>>n>>m>>p;++n;++m;
for(int i = 0;i < n;i++) cin>>F[i];
for(int i = 0;i < m;i++) cin>>G[i];
getrev(m + n);
for(int i = 0;i < 3;i++)
{
memset(a,0,sizeof(a));memset(b,0,sizeof(b));memset(ans[i],0,sizeof(ans[i]));
for(int j = 0;j < n;j++) a[j] = F[j];
for(int j = 0;j < m;j++) b[j] = G[j];
NTT(a,1,i);NTT(b,1,i);
for(int j = 0;j < tt;j++) ans[i][j] = 1ll * a[j] * b[j] % MOD[i];
NTT(ans[i],-1,i);
}
for(int i = 0;i < n + m;i++) ans[3][i] = crt(ans[0][i],ans[1][i],ans[2][i]);
for(int i = 0;i < n + m - 1;i++) cout<<ans[3][i]<<" ";
return 0;
}
分治NTT
给定
其中
这个
假设我们已经计算了
inline void cdqNTT(int *x,int *y,int l,int r)//[l,r)
{
if(l == r - 1)
{
if(!l) x[l] = 1;
return;
}
int mid = (l + r) >> 1;
cdqNTT(x,y,l,mid);
getrev(r - l);
for(int i = 0;i < mid - l;i++) a[i] = x[i + l];
fill(a + mid - l,a + tt,0);
for(int i = 0;i < r - l;i++) b[i] = y[i];
fill(b + r - l,b + tt,0);
NTT(a,1);NTT(b,1);
for(int i = 0;i < tt;i++) a[i] = 1ll * a[i] * b[i] % MOD;
NTT(a,-1);
for(int i = mid;i < r;i++) x[i] = (x[i] + a[i - l]) % MOD;
cdqNTT(x,y,mid,r);
}
完结撒花
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】