多项式:从入门到全家桶
第一次用博客园写学习笔记,写的不好请见谅~
欢迎各位OIer在评论区说一下自己对这篇博客的建议!
有什么问题也欢迎在下方评论区中提出!顺便点赞
有关多项式的基本概念
对于求和式
一个多项式的度(也称次数)就是这个多项式最高次项的次数,记作
多项式的表示一般有两种方法:
- 系数表示,就是形如
的形式。 - 点值表示,即给出
个点 ,求一个 次多项式使得这 个点都在 上。
定义加法和乘法以后,多项式的基本运算和整数基本一致。(这个大家都会吧)
多项式的导数公式:
多项式的积分公式:
一个长度为
多项式乘法
单位根(前置知识)
前置知识:复数
顾名思义,单位根就是满足
众所周知,在复数域内,
由于复数乘法的几何意义是模长相乘,辐角相加,因此
为方便,我们设
单位根有三个重要的性质。对于任意正整数
这三个性质在快速傅里叶变换中会被用到。
顺便一提,单位根还有一个比较重要的性质,是:
DFT
又称离散傅里叶变换、Discrete Fourier Transform。
DFT的思想是将两个多项式的系数表示与点值表示互相转化。
因为多项式的系数表示直接相乘是
先考虑将系数表示转化为点值。
如果暴力去求,时间复杂度还是
那这个时候我们就要用到一个东西叫做:
FFT
又称快速傅里叶变换、Fast (Discrete) Fourier Transform。
我们先把多项式的次数
接下来,我们先尝试将系数表示转化为点值表示,这一步就被称之为FFT。
我们考虑一个
以
我们先按照次数的奇偶来分成两组,然后右边提出来一个 x:
然后再用奇偶次次项数建立新的函数:
那么原来的
于是我们可以发现一个性质:它可以分治!
也就是说,我们可以先将原来的式子的奇数项和偶数项分别处理(即分别计算FFT),求出原文中的
那怎么合并呢?
由于
但是,我们可以再考虑以下这个式子:
这样的话,我们能用这个式子求出另外
你以为这就完了吗?还没有!
观察上面两个式子,我们发现:它只能求互为相反数的
于是我们考虑单位根。
单位根有很好的的对称性,并且满足平方还是单位根这一特性。(因为
如果我们把
(注意到之前我们让
于是递归即可。
边界条件:
时间复杂度:
逆FFT(IFFT)
现在我们已经解决了系数表示转换为点值表示,那么我们怎么将点值表示转换为系数表示呢?
我们知道有一个东西叫做高斯消元逆FFT。
它的证明需要依赖矩阵,这里不详细说明。(主要是作者也不是很会)
简单来说,你只需要把之前FFT的过程变成这样:
(就是系数变成它的共轭复数)
然后最终得出来的结果除以
好的,到了这一步,我们已经会多项式乘法的基本操作了。
但是别着急,后面还有一个优化:
非递归写法
虽然 FFT 和 IFFT 都是
那我们应该怎么解决这个问题呢?
我们观察到 FFT 需要大量的复制数组的操作,这使得它的常数非常大。
有没有通过不移动数就能求出 FFT 的方法呢?
当然有。
我们先把整个过程压到一个数组中,再考虑之前的 FFT 都把数移到了哪些位置:(还是以
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
第一层 | [0 | 1 | 2 | 3 | 4 | 5 | 6 | 7] |
第二层 | [0 | 2 | 4 | 6] | [1 | 3 | 5 | 7] |
第三层 | [0 | 4] | [2 | 6] | [1 | 5] | [3 | 7] |
边界 | [0] | [4] | [2] | [6] | [1] | [5] | [3] | [7] |
可以看出最后一层每一位都是这个位置下标的二进制翻转过来。
而我们如果最开始把边界数组存下来,再将固定距离的数进行合并即可。
于是我们可以先预处理出每个数的二进制翻转后的结果
然后从最下面一层开始枚举,每层距离变为原来的
这样直接合并,时间复杂度仍为
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const double Pi=acos(-1);//用这个式子算pi要好一些
inline int read()
{
char ch=getchar();int x=0,r=1;
while(ch<'0'||ch>'9'){if(ch=='-')r=0;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return r?x:-x;
}
struct complexs
{
double x,y;
complexs(double xx=0,double yy=0){x=xx;y=yy;}
complexs operator +(double b){return {x+b,y};}
complexs operator -(double b){return {x-b,y};}
complexs operator *(double b){return {x*b,y*b};}
complexs operator /(double b){return {x/b,y/b};}
complexs operator +(complexs b){return {x+b.x,y+b.y};}
complexs operator -(complexs b){return {x-b.x,y-b.y};}
complexs operator *(complexs b){return {x*b.x-y*b.y,x*b.y+y*b.x};}
complexs operator /(complexs b){return (complexs){x*b.x+y*b.y,-x*b.y+y*b.x}/(b.x*b.x+b.y*b.y);}
};//虽然看上去很复杂但实际上就是简单的复数运算
typedef vector<complexs> vc;//vc就是vector<complexs>
int rev[2100000];//二进制翻转数组
void FFT(vc &x,int limit,bool type=1)//type=1是FFT,否则是IFFT
{
for(int i=0;i<limit;++i)if(i<rev[i])swap(x[i],x[rev[i]]);//初始边界数组
for(int len=1;len<limit;len<<=1)
{
complexs Wn(cos(Pi/len),(2*type-1)*sin(Pi/len));//单位根,注意IFFT的时候单位根要取反
for(int i=0;i<limit;i+=2*len)//枚举每段左边界
{
complexs W(1,0),X,Y;
for(int j=i;j<i+len;++j)
{
X=x[j];Y=W*x[j+len];
x[j]=X+Y;x[j+len]=X-Y;//合并
W=W*Wn;
}
}
}
if(!type)for(int i=0;i<=limit;++i)x[i]=x[i]/limit;//IFFT时每个数要除以n
}
int limit;
void operator *=(vc &a,vc b)
{
int x=a.size()+b.size()-1;
limit=1;
while(1ull*limit<=a.size()-1+b.size()-1)limit<<=1;//注意limit是要大于等于项数,不是大于等于次数,项数=次数+1
for(int i=0;i<limit;++i)rev[i]=(rev[i/2]/2+(i%2)*limit/2);//计算二进制翻转
while(a.size()<1ull*limit)a.push_back(0);
while(b.size()<1ull*limit)b.push_back(0);
FFT(a,limit);FFT(b,limit);//系数转换成点值
for(int i=0;i<limit;++i)a[i]=a[i]*b[i];//点值相乘
FFT(a,limit,0);//点值转换成系数
while(a.size()>1ull*x)a.pop_back();
}
vc a,b;
int n,m;
signed main()
{
n=read();m=read();
for(int i=0;i<=n;++i)a.push_back({1.0*read(),0});
for(int i=0;i<=m;++i)b.push_back({1.0*read(),0});
a*=b;
for(complexs i:a)printf("%lld ",(int)(i.x+0.5));//精度丢失较大,建议四舍五入
return 0;
}
三次变两次优化
省流:一般不用。
假设我们要求
于是
发现它的虚部除以
NTT
又称快速数论变换、Number-Theoretic Transform。(在有的地方也被称为FNTT?总之都是这个意思)
虽然FFT好用,但它也有一些致命的弱点,例如复数乘法和三角函数常数太大,精度不高,不支持取模等等。
那我们能解决这些问题吗?不能
当然可以,于是我们就有了 NTT。
回忆一下FFT之所以用单位元是用了它的哪些性质。
(确定点对个数时) (倍增时) (合并时) (写起来方便)
于是我们发现数论中的原根也有这个性质。
前置知识:原根(你只要知道有这么个东西就行了,细节可以不用看)
更确切地说,我们设原根为
利用数论,它可以解决精度和取模问题。
其它代码基本和 FFT 一样。
注意:NTT不能用三次变两次优化!
证明
第一条由原根的定义,只要原根的指数不同那么值也不同。
由于
总是整数(并且 一定时是常数),且 值不一致,故成立。 第二条:
第三条:
并且
证毕。
代码(用vector实现)
#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef vector<int> vi;//vi指vector<int>
const int mod=998244353,g=3,ig=332748118;//ig是g的逆元
inline int read()
{
char ch=getchar();int x=0,r=1;
while(ch<'0'||ch>'9'){if(ch=='-')r=0;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return r?x:-x;
}
int mul(int x,int b)//快速幂
{
int ans=1;
while(b)
{
if(b&1)ans=(ans*x)%mod;
x=(x*x)%mod;
b>>=1;
}
return ans;
}
int rev[2100000];
void NTT(vi &x,int limit,bool type=1)
{
for(int i=0;i<limit;++i)if(i<rev[i])swap(x[i],x[rev[i]]);
for(int len=1;len<limit;len<<=1)
{
int Wn=mul(type?g:ig,(mod-1)/(2*len));//利用原根代替FFT中的单位元
for(int i=0;i<limit;i+=2*len)
{
int W=1,X,Y;
for(int j=i;j<i+len;++j)
{
X=x[j];Y=W*x[j+len]%mod;
x[j]=(X+Y)%mod;x[j+len]=(X-Y+mod)%mod;//合并同FFT,但是要加上取模运算
W=(W*Wn)%mod;
}
}
}
if(!type)
{
int invs=mul(limit,mod-2);
for(int i=0;i<limit;++i)x[i]=(x[i]*invs)%mod;
}
}
int limit;
void operator *=(vi &a,vi b)
{
int x=a.size()+b.size()-1;
limit=1;
while(1ull*limit<=a.size()-1+b.size()-1)limit<<=1;
for(int i=0;i<limit;++i)rev[i]=(rev[i/2]/2+(i%2)*limit/2);
while(a.size()<1ull*limit)a.push_back(0);
while(b.size()<1ull*limit)b.push_back(0);
NTT(a,limit);NTT(b,limit);//FFT变为NTT
for(int i=0;i<limit;++i)a[i]=(a[i]*b[i])%mod;
NTT(a,limit,0);
while(a.size()>1ull*x)a.pop_back();
}
vi a,b;
int n,m;
signed main()
{
n=read();m=read();
for(int i=0;i<=n;++i)a.push_back(read());
for(int i=0;i<=m;++i)b.push_back(read());
a*=b;
for(int i:a)printf("%lld ",i);
return 0;
}
MTT
省流:解决任意模数的问题,一般不会用,但出现时比较恶心,可以写多次卷积+CRT(中国剩余定理),就是常数容易爆。
我们发现NTT只适用于模数为
那如果模数不是
解法一
假设原来的多项式值域是
考虑对多个模数卷积(
解法二
考虑拆系数。
我们先顺手把每个系数先模上
把系数拆成
把
于是
对这个式子的
并且我们不能用可爱的NTT了,这意味着我们要面对FFT的精度问题(毕竟上界是
以及,千万不要相信自己背
解法三
我们考虑在解法二的基础上进行优化。
我们现在有四个多项式
上面的三次变两次优化告诉我们:有的时候塞一个复多项式进去可以把常数变得更小。
不妨设
则
再取
因此,我们对
还是要写
多项式的各种运算
多项式乘法逆(倍增法)
多项式乘法逆的定义:对于一个
我们还是考虑递归。
首先便于计算,我们还是把
由于高次项的系数不会影响低次项的逆元,我们这步“扩大”可以直接补
为了下文叙述方便,我们记
我们可以先求出
于是我们有:
将最后一个式子平方可得:
两边同乘
移项得:
倍增即可。
时间复杂度:
代码
//没错我又更了一堆插件
void read(int n,vi &a){for(int i=0;i<=n;++i)a.push_back(read());}//输入,n为次数,或者项数+1
void print(vi &a){for(int i:a)printf("%lld ",i);puts("");}//输出
void cut(vi &a,int x){while(a.size()>1ull*x)a.pop_back();while(a.size()<1ull*x)a.push_back(0);}//将a的系数个数强制设为x,注意系数个数和deg是两个不同的东西
void copy(vi &a,int x,vi &b)//将a的前x个系数复制到b,不足补0
{
if(1ull*x<=a.size()){b.clear();for(int i=0;i<x;++i)b.push_back(a[i]);}
else {b=a;while(b.size()<1ull*x)b.push_back(0);}
}
void operator +=(vi &a,int x){a.front()=(a.front()+x)%mod;}//多项式+常数
void operator +=(vi &a,vi &b)//多项式相加
{
while(a.size()<b.size())a.push_back(0);
for(int i=0;1ull*i<b.size();++i)a[i]=(a[i]+b[i])%mod;
}
void operator --(vi &a){for(int i=0;1ull*i<a.size();++i)a[i]=(mod-a[i])%mod;}//将多项式取负,相当于0-a
void operator -=(vi &a,int x){a.front()=(a.front()-x+mod)%mod;}//多项式-常数
void operator -=(vi &a,vi &b)//多项式相减
{
while(a.size()<b.size())a.push_back(0);
for(int i=0;1ull*i<b.size();++i)a[i]=(a[i]-b[i]+mod)%mod;
}
vi inv(vi &a)//逆多项式部分
{
int x=1;
vi ans={mul(a.front(),mod-2)},b,now;//显然a对于x^1的逆就是a0的逆元
while(1ull*x<a.size())
{
x<<=1;//x为系数个数
copy(a,x,b);//将a的后几位存下来,避免ans每次直接与a相乘,影响时间复杂度
now=ans;ans*=b;ans-=2;--ans;ans*=now;//按式子进行计算,注意代码中--的定义与整数不同
cut(ans,x);//去除多余位
}
cut(ans,a.size());//让ans次数和a一样
return ans;
}
多项式除法
顾名思义,就是一个多项式
我们直接对

好吧,真正的多项式除法实际上是带鱼带余除法。
或者说得正式点:给定一个
,
要解决这样一个问题,我们首先构造这样一个变换:
观察到这个变换的实质是反转
并且实际上有
(注:
由
两边同时乘上
注意到我们只需要求出
我们发现上式中
因此我们把式子两边模上
于是多项式求逆解决
注意要先把
时间复杂度
代码
typedef pair<vi,vi> pvv;//除法返回答案时要用pair,你也可以设几个全局变量
#define mk make_pair
#define F first
#define S second
void R(vi &a){reverse(a.begin(),a.end());}//简单的reverse
pvv operator /(vi a,vi b)
{
vi q,r;
q=b;R(q);cut(q,a.size()-b.size()+1);//把q设为bR,把q的次数设为n-m(size设为n-m+1)
r=a;R(r);//r作为临时变量存储aR
q=inv(q);q*=r;//计算qR
cut(q,a.size()-b.size()+1);R(q);//将qR多余位数截掉后还原q
r=q;r*=b;r-=a;--r;cut(r,b.size()-1);//计算r
return mk(q,r);
}
多项式开方
给定一个
设
首先我们先求出
代入
于是我们取
(由于
以下我们先假定
老规矩,我们继续把
假设我们求出了
记
两边同时平方,得:
将
两边同时开方:
倍增计算即可。
时间复杂度:
代码
vi sqrt(vi &a)
{
int x=1;
vi ans={1},b,now;
while(1ull*x<a.size())
{
x<<=1;
cut(ans,x);//这里求逆的时候要
copy(a,x,b);
now=ans;ans=inv(ans);ans*=b;ans+=now;ans*=i2;
cut(ans,x);
}
cut(ans,a.size());
return ans;
}
(为了直观,我写的常数比较大,建议读者写的时候不要每次都做一遍cut和copy,建议多传几个参数)
然后我们解决
若
否则,我们考虑原式中因式
如果
分治FFT
经典例题:给你一个数组
我们先来对比一下普通卷积和分治FFT的式子:
普通卷积:
分治FFT:
我们发现:分治FFT实际上和普通卷积比较像,就是输出的数组就是它本身。
但这个性质好像没啥卵用
我们发现,对于每个区间
- 递归
算出递归区间内的答案。 - 把
的贡献加到 上。 - 递归
。
其中第二步我们再详细讲一下。
假设当前区间为
于是,
卷积即可。
时间复杂度:
代码
void copy(vi &a,int l,int r,vi &b)//把a的第l位到第r位复制给b
{
b.clear();
for(int i=l;i<=r;++i)b.push_back(a[i]);
}
void div_FFT(vi &a,vi &b,int l=-1,int r=-1)
{
if(l==-1&&r==-1)
{
cut(a,b.size());
l=0;r=b.size()-1;//初始化
}
if(l==r)return;
int mid=(l+r)>>1;
div_FFT(a,b,l,mid);//分治左区间
vi c,d;
copy(b,1,r-l,c);copy(a,l,mid,d);
c*=d;
for(int i=mid+1;i<=r;++i)a[i]=(a[i]+c[i-l-1])%mod;//卷积处理对右半的贡献
div_FFT(a,b,mid+1,r);//分治右区间
}
但是先别走!这道题也可以用多项式求逆解决!!!
我们发现
由于
并且
则有
时间复杂度
多项式
各位都知道整数的
后面的证明需要依赖微积分,不会的同学请自行跳过。
实际上,
但是,直接用定义式时间复杂度会炸。
首先,对于多项式
我们知道
再同时积分:
因此我们只需要多项式求导、积分、求逆和乘积就可以了。
时间复杂度
代码
int invv[2100000];
void init(int n=2099999)
{
invv[1]=1;
for(int i=2;i<=n;++i)invv[i]=(mod-mod/i)*invv[mod%i]%mod;
}//线性求逆元
void D(vi &a){for(int i=1;1ull*i<a.size();++i)a[i-1]=i*a[i]%mod;a.pop_back();}//求导
void I(vi &a){a.push_back(0);for(int i=a.size()-1;i>=1;--i)a[i]=invv[i]*a[i-1]%mod;a[0]=0;}//积分
void ln(vi &a)
{
vi b=inv(a);
D(a);
a*=b;I(a);
cut(a,b.size());
}
多项式
首先,对于多项式
我们知道
两边求导得:
提取系数后就变成了
由多项式的求导,有
即
并且
虽然和分治FFT的模板不同,但实际上差不多,我们把
其实,把上述式子再变换也可以求出
时间复杂度
代码
void div_FFT(vi &a,vi &b,bool isexp=0,int l=-1,int r=-1)//对之前的分治FFT做了点手脚
{
if(l==-1&&r==-1)
{
if(a.empty())a.push_back(1);
cut(a,b.size());
l=0;r=b.size()-1;
}
if(l==r){if(isexp&&l)a[l]=(a[l]*invv[l])%mod;return;}//边界条件略改一下
int mid=(l+r)>>1;
div_FFT(a,b,isexp,l,mid);
vi c,d;
copy(b,1,r-l,c);copy(a,l,mid,d);
c*=d;
for(int i=mid+1;i<=r;++i)a[i]=(a[i]+c[i-l-1])%mod;
div_FFT(a,b,isexp,mid+1,r);
}
vi exp(vi &a)
{
for(int i=0;1ull*i<a.size();++i)a[i]=(a[i]*i)%mod;
vi b={};
div_FFT(b,a,1);//1代表分治FFT最终的系数要除以下标
return b;
}
多项式快速幂
我们考虑封装多项式乘法,再倍增即可。

好吧,正解是这样的:
我们先保证
众所周知,多项式的
于是我们输出
时间复杂度
但这还没完!
我们发现洛谷上的模板题是这样的:
那怎么办呢?还要写个高精?
肯定不可能,我们需要知道一个性质:
这个式子在
证明
我们先证明一下这样一个结论:
。 显然有个式子叫做
。(组合数拆分系数) 不妨设上述结论对所有
次多项式成立。 设
的次数为 ,并且 , 则
。 有了这个式子以后,由于
,所以 ,证毕。 有些同学可能到了这里还有一个疑问:为什么整数快速幂的时候指数要模
,但多项式快速幂就变成模 了呢? 换句话说,如果把
带入具体的值,那么模数应该模上 ,而不是 。 其实,这两个式子还是有一点差别的。
快速幂的式子是:
而在
的条件下,多项式快速幂的式子是: 也就是说一个式子算完了,而另一个式子只计算了前
项,因此它们推导的方式也不同。 一定要注意多项式快速幂的证明要依赖
这个限制。
有了这个性质,我们就能很轻松地解决刚开始的问题了,只需要在读入的时候将
啥?怎么在读入的时候将
啥?还不会?快读每次乘
代码
inline int read(int mod=0)//修改后的快读,加入了取模
{
char ch=getchar();int x=0,r=1;
while(ch<'0'||ch>'9'){if(ch=='-')r=0;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';if(mod)x%=mod;ch=getchar();}//这里有改动
return r?x:-x;
}
void mul(vi &a,int k)
{
ln(a);
a*=k;
a=exp(a);
}
接下来我们考虑
当
对于
注意这里要同时保留
多项式全家桶代码
由于之前的代码段太多了,函数也比较多,所以这里整合了一下。
建议大家先把所有的函数写完再一起卡常,防止自己看不懂
全家桶代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef vector<int> vi;
typedef pair<vi,vi> pvv;
#define mk make_pair
#define F first
#define S second
const int mod=998244353,g=3,ig=332748118,i2=499122177;
inline int read(int mod=0)
{
char ch=getchar();int x=0,r=1;
while(ch<'0'||ch>'9'){if(ch=='-')r=0;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';if(mod)x%=mod;ch=getchar();}
return r?x:-x;
}
int mul(int x,int b)
{
int ans=1;
while(b)
{
if(b&1)ans=(ans*x)%mod;
x=(x*x)%mod;
b>>=1;
}
return ans;
}
int invv[2100000];
void init(int n=2099999)
{
invv[1]=1;
for(int i=2;i<=n;++i)invv[i]=(mod-mod/i)*invv[mod%i]%mod;
}
int rev[2100000];
void read(int n,vi &a,int l=0){for(int i=0;i<=l+n-1;++i)a.push_back(i<l?0:read());}
void print(vi &a){for(int i:a)printf("%lld ",i);puts("");}
void D(vi &a){for(int i=1;1ull*i<a.size();++i)a[i-1]=i*a[i]%mod;a.pop_back();}
void I(vi &a){a.push_back(0);for(int i=a.size()-1;i>=1;--i)a[i]=invv[i]*a[i-1]%mod;a[0]=0;}
void NTT(vi &x,int limit,bool type=1)
{
for(int i=0;i<limit;++i)if(i<rev[i])swap(x[i],x[rev[i]]);
for(int len=1;len<limit;len<<=1)
{
int Wn=mul(type?g:ig,(mod-1)/(2*len));
for(int i=0;i<limit;i+=2*len)
{
int W=1,X,Y;
for(int j=i;j<i+len;++j)
{
X=x[j];Y=W*x[j+len]%mod;
x[j]=(X+Y)%mod;x[j+len]=(X-Y+mod)%mod;
W=(W*Wn)%mod;
}
}
}
if(!type)
{
int invs=mul(limit,mod-2);
for(int i=0;i<limit;++i)x[i]=(x[i]*invs)%mod;
}
}
int limit;
void cut(vi &a,int x){while(a.size()>1ull*x)a.pop_back();while(a.size()<1ull*x)a.push_back(0);}
void copy(vi &a,int x,vi &b)
{
if(1ull*x<=a.size()){b.clear();for(int i=0;i<x;++i)b.push_back(a[i]);}
else {b=a;while(b.size()<1ull*x)b.push_back(0);}
}
void copy(vi &a,int l,int r,vi &b)
{
b.clear();
for(int i=l;i<=r;++i)b.push_back(a[i]);
}
void operator +=(vi &a,int x){a.front()=(a.front()+x)%mod;}
void operator +=(vi &a,vi &b)
{
while(a.size()<b.size())a.push_back(0);
for(int i=0;1ull*i<b.size();++i)a[i]=(a[i]+b[i])%mod;
}
void operator -=(vi &a,int x){a.front()=(a.front()-x+mod)%mod;}
void operator -=(vi &a,vi &b)
{
while(a.size()<b.size())a.push_back(0);
for(int i=0;1ull*i<b.size();++i)a[i]=(a[i]-b[i]+mod)%mod;
}
void operator *=(vi &a,int x){for(int i=0;1ull*i<a.size();++i)a[i]=((a[i]*x)%mod+mod)%mod;}
void operator *=(vi &a,vi b)
{
int x=a.size()+b.size()-1;
limit=1;
while(1ull*limit<=a.size()-1+b.size()-1)limit<<=1;
for(int i=0;i<limit;++i)rev[i]=(rev[i/2]/2+(i%2)*limit/2);
while(a.size()<1ull*limit)a.push_back(0);
while(b.size()<1ull*limit)b.push_back(0);
NTT(a,limit);NTT(b,limit);
for(int i=0;i<limit;++i)a[i]=(a[i]*b[i])%mod;
NTT(a,limit,0);
cut(a,x);
}
vi inv(vi &a)
{
int x=1;
vi ans={mul(a.front(),mod-2)},b,now;
while(1ull*x<a.size())
{
x<<=1;
copy(a,x,b);
now=ans;ans*=b;ans*=-1;ans+=2;ans*=now;
cut(ans,x);
}
cut(ans,a.size());
return ans;
}
void R(vi &a){reverse(a.begin(),a.end());}
pvv operator /(vi a,vi b)
{
vi q,r;
q=b;R(q);cut(q,a.size()-b.size()+1);
r=a;R(r);
q=inv(q);q*=r;
cut(q,a.size()-b.size()+1);R(q);
r=q;r*=b;r-=a;r*=-1;cut(r,b.size()-1);
return mk(q,r);
}
vi sqrt(vi &a)
{
int x=1;
vi ans={1},b,now;
while(1ull*x<a.size())
{
x<<=1;
cut(ans,x);copy(a,x,b);
now=ans;ans=inv(ans);ans*=b;ans+=now;ans*=i2;
cut(ans,x);
}
cut(ans,a.size());
return ans;
}
void div_FFT(vi &a,vi &b,bool isexp=0,int l=-1,int r=-1)
{
if(l==-1&&r==-1)
{
if(a.empty())a.push_back(1);
cut(a,b.size());
l=0;r=b.size()-1;
}
if(l==r){if(isexp&&l)a[l]=(a[l]*invv[l])%mod;return;}
int mid=(l+r)>>1;
div_FFT(a,b,isexp,l,mid);
vi c,d;
copy(b,1,r-l,c);copy(a,l,mid,d);
c*=d;
for(int i=mid+1;i<=r;++i)a[i]=(a[i]+c[i-l-1])%mod;
div_FFT(a,b,isexp,mid+1,r);
}
void ln(vi &a)
{
vi b=inv(a);
D(a);
a*=b;I(a);
cut(a,b.size());
}
vi exp(vi &a)
{
for(int i=0;1ull*i<a.size();++i)a[i]=(a[i]*i)%mod;
vi b={};
div_FFT(b,a,1);
return b;
}
void mul(vi &a,int k)
{
ln(a);
a*=k;
a=exp(a);
}
signed main()
{
return 0;
}
多项式牛顿迭代
开个坑,以后再写。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下