多项式算法学习笔记
定义
形如 的式子被称为多项式。定义多项式中每个单项式叫做这个多项式的项,定义多项式的次数为项的最高次数。显然
个点可以唯一确定一个次数为
的多项式。
接下来我们首先来看一下多项式的基本运算怎么实现(要不然谁用啊)
多项式加减
直接对齐相同次数然后系数直接加减就可以了吧。。。时间复杂度 ,难度:普及组 T1(虽然这个级别已经不存在了233)
多项式乘法
首先我们考虑一下我们平时如何手算多项式算法。显然我们都是 暴力算的,所以就有了一个枚举所有可能的二元组然后统计答案的暴力做法。
当然在常规题目中我们会使用一种叫做 FFT(快速傅里叶变换) 的 的优秀算法来计算,详情请查看这篇文章。
哈哈你一定学完 FFT 然后回来了!我们来考虑一下如果多项式乘法在模意义下怎么做。
有一种叫做 NTT (快速数论变换)的东西,它把每次我们做快速傅里叶变换时用到的 重新定义了一下。大概是令
为模数
的原根,我们就可以取
。然后 IDFT 的时候直接乘逆元就可以了。
至于这个原根怎么找。。。一般你碰到模数为 的时候就想下可不可以 NTT,因为
是一个常用的 NTT 模数,一个原根是
。
中心代码大致如下:
inline void NTT(int *A,int limit,int opt){
FOR(i,0,limit-1) if(i < r[i]) std::swap(A[i],A[r[i]]);
for(int mid = 1;mid < limit;mid <<= 1){
int W = qpow(3,(ha-1)/(mid<<1));
if(opt == -1) W = qpow(W,ha-2);
for(int j = 0;j < limit;j += (mid<<1)){
for(int k = 0,w = 1;k < mid;k++,w = w*W%ha){
int x = A[j+k],y = w*A[j+mid+k]%ha;
A[j+k] = (x+y)%ha;A[j+mid+k] = (x-y+ha)%ha;
}
}
}
if(opt == -1){ // IDFT
int inv = qpow(limit,ha-2);
FOR(i,0,limit-1) A[i] = A[i]*inv%ha;
}
} 好了接下来才是关于多项式的一些基本的稍微硬核一点的操作。
确定多项式
上面提到了 个点可以唯一确定一个次数为
的多项式。
现在给定 个点,请你确定这个多项式并且将
代入求值。
做法一:高斯消元
我们暴力建方程然后暴力高斯消元!。
什么你连高斯消元都不会?题目链接
其实就是暴力解方程啦~奉上高斯消元模板题的代码:
#include <algorithm>
#include <iostream>
#include <cstring>
#include <climits>
#include <cstdio>
#include <vector>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <set>
#define fi first
#define lc (x<<1)
#define se second
#define U unsigned
#define rc (x<<1|1)
#define Re register
#define LL long long
#define MP std::make_pair
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(Re int i = a;i <= b;++i)
#define ROF(i,a,b) for(Re int i = a;i >= b;--i)
#define SFOR(i,a,b,c) for(Re int i = a;i <= b;i+=c)
#define SROF(i,a,b,c) for(Re int i = a;i >= b;i-=c)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const double EPS = 1e-7;
const int MAXN = 200+5;
double d[MAXN][MAXN],ans[MAXN];
int n;
int main(){
scanf("%d",&n);
FOR(i,1,n) FOR(j,1,n+1) scanf("%lf",&d[i][j]);
FOR(i,1,n){
int r = i;
FOR(j,i+1,n) if(std::fabs(d[r][i]) < std::fabs(d[j][i])) r = j;
if(std::fabs(d[r][i]) < EPS){
puts("No Solution");
return 0;
}
if(i != r) std::swap(d[r],d[i]);
double div = d[i][i];
FOR(j,i,n+1) d[i][j] /= div;
FOR(j,i+1,n){
div = d[j][i];
FOR(k,i,n+1) d[j][k] -= d[i][k]*div;
}
}
ans[n] = d[n][n+1];
ROF(i,n-1,1){
ans[i] = d[i][n+1];
FOR(j,i+1,n) ans[i] -= (d[i][j]*ans[j]);
}
FOR(i,1,n) printf("%.2f\n",ans[i]);
return 0;
} 做法二:拉格朗日插值
背下式子:
设该多项式为 ,第
个点的坐标为
,需要求在
点的取值。
证明显然:因为这个式子满足了如果代入 就肯定会返回
。根据
个点唯一确定一个多项式,就可以知道这个多项式等价于原多项式了。
#include <algorithm>
#include <iostream>
#include <cstring>
#include <climits>
#include <cstdio>
#include <vector>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <set>
#define fi first
#define lc (x<<1)
#define se second
#define U unsigned
#define rc (x<<1|1)
#define Re register
#define LL long long
#define MP std::make_pair
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(Re int i = a;i <= b;++i)
#define ROF(i,a,b) for(Re int i = a;i >= b;--i)
#define SFOR(i,a,b,c) for(Re int i = a;i <= b;i+=c)
#define SROF(i,a,b,c) for(Re int i = a;i >= b;i-=c)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const int MAXN = 2000+5;
const int ha = 998244353;
inline int qpow(int a,int n=ha-2){
int res = 1;
while(n){
if(n & 1) res = 1ll*res*a%ha;
a = 1ll*a*a%ha;
n >>= 1;
}
return res;
}
int n,k,x[MAXN],y[MAXN],ans;
int main(){
scanf("%d%d",&n,&k);
FOR(i,1,n) scanf("%d%d",x+i,y+i);
FOR(i,1,n){
int s1 = 1,s2 = 1;
FOR(j,1,n){
if(j == i) continue;
s1 = 1ll*s1*((1ll*k-x[j]+ha)%ha)%ha;s2 = 1ll*s2*((1ll*x[i]-x[j]+ha)%ha)%ha;
}
s2 = qpow(s2);s1 = 1ll*s1*s2%ha*y[i]%ha;
ans = (ans+s1)%ha;
}
printf("%d\n",ans);
return 0;
} 扩展阅读:维基百科-拉格朗日插值
多项式求逆
题目链接
给定一个多项式 ,让你求出一个多项式
,满足
我们可以考虑倍增地构造多项式 。首先我们把这个式子进行一些化简。
不难发现最后一个式子的范围扩大到原来的 倍,并且又凑成了一开始的形式。
我们考虑迭代处理,首先当规模为 时显然答案系数就是对应系数的逆元。
我们设对于规模为 的问题得到的多项式为
,我们想要推出扩大一倍规模的答案
,根据上面的推导,可以有
所以我们直接两遍 NTT 就能扩大一倍的问题规模了。
时间复杂度:。
这里就只给迭代写法啦 qwq 貌似迭代确实比递归快很多呢。
inline void Inv(int *A,int *B,int n){
B[0] = qpow(A[0],ha-2);
int len;
for(len = 1;len < (n<<1);len <<= 1){
int d = len<<1;
FOR(i,0,len-1) a[i] = A[i],b[i] = B[i];
FOR(i,0,d-1) pos[i] = (pos[i>>1]>>1)|((i&1)?len:0);
NTT(a,d,1);NTT(b,d,1);
FOR(i,0,d-1) B[i] = ((2-a[i]*b[i]%ha+ha)%ha*b[i]%ha)%ha;
NTT(B,d,-1);
FOR(i,len,d-1) B[i] = 0;
}
FOR(i,0,len-1) a[i] = b[i] = 0;
FOR(i,n,len-1) B[i] = 0;
} 多项式除法
题目链接
给定一个 次多项式
和一个
次多项式
,求出两个多项式
,满足:
次数为
,
次数小于
。
。
显然如果能整除的话我们直接求个逆就可以了。那现在有余数怎么办啊 Orz.......
别急,我们首先定义一种操作 R(reverse),满足:。
不难看出操作的本质是多项式翻转。所以我们就可以
完成这个操作。
现在我们尝试对我们要构造的东西做一些改变。
我们考虑两边对取模。
我们求一遍的逆,然后用 NTT 乘起来,就可以得到
了,最后
直接计算出来余数就可以了。
时间复杂度瓶颈在于多项式求逆,所以是。
代码就不贴了。就是上面的一些简单运用。多项式求导/积分
都是非常简单的东西了。
根据求导的加法法则和幂函数可以轻松得到:
补充一些同样适用于多项式的求导基本法则:
写这些主要是为了介绍后面的牛顿迭代
多项式牛顿迭代
处理多项式复合的高级神器 Orz
问题大概是这样的:给出 ,求
满足
。
首先为了让像我这样萌萌哒的弱校初中选手也能看懂,我决定放一些前置芝士。
前置芝士1:Taylor 展开
这个大家都会 qwq。
Taylor 展开(泰勒展开)是一种用函数在某点的信息描述其附近取值的公式,也就是拟合某点及其周围的取值啦qwq。
具体来说我们从头开始构造这个拟合函数,设我们要使 及其周围一段区间拟合这个原函数。首先我们可以使得我们构造出的函数和待拟合的函数在
的取值相等。但是发现这只有一个值?一点也不像怎么办?我们可以使得他们的二阶倒数,三阶导数.....一直相等下去,然后我们就会发现这两个函数在那附近神奇的重合了!我们看个 GIF 来详细的了解这个过程。

所以我们就可以得到 Taylor 展开来拟合多项式的式子了:
其中 表示对原函数图像
这个点进行
阶求导。
如果描述听不懂的话建议大家去看一下上面的视频的...毕竟视频里有图还有可爱的 ~
回到多项式牛顿迭代上
我们考虑使用和多项式求逆一样的 trick:倍增来构造答案多项式。
首先当 时,
,这个就是要单独求出来的。
现在我们假设已经求出了
考虑如何扩展到 下,我们把
放到
上做 Taylor 展开
因为倍增构造,我们可以发现 和
后
项相同,所以
的最低非
项次数大于
。所以可以得到
然后因为我们要求的 需要满足
,所以可以推一波式子:
按照多项式求逆那样的套路迭代计算就可以了,我们来看一下一些经典的题目。
多项式开方
题目链接
给定一个多项式 ,要求求出一个多项式
,满足
简单的变换:。
设 ,则
,使用牛顿迭代法:
然后就可以直接迭代计算了。时间复杂度 。
多项式求 ln
题目链接
给定一个多项式 ,求一个多项式
,满足
。
我们设 ,定义
。
那么显然 。
那么求逆就可以算出 。然后因为求导和不定积分互逆,所以对
求一个不定积分就可以了。
代码不贴了(主要是后面有大杂烩)
多项式求 exp
题目链接
多项式大杂烩,练习之前学的怎么样了qwq
给定一个多项式 ,要你求出一个
,满足
。
我们考虑计算出
变形一下可得
设 ,然后直接套用牛顿迭代方法:
首先求导:得到
然后代入牛顿迭代公式,得到
因为 ,所以
的常数项是
。(有些牛顿迭代题目要用二次剩余算系数但是我还没接触过QAQ)
然后就是板子大全了qwq
放一下代码吧:
#include <algorithm>
#include <iostream>
#include <cstring>
#include <climits>
#include <cstdio>
#include <vector>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <set>
#define Re register
#define LL long long
#define U unsigned
#define FOR(i,a,b) for(Re int i = a;i <= b;++i)
#define ROF(i,a,b) for(Re int i = a;i >= b;--i)
#define SFOR(i,a,b,c) for(Re int i = a;i <= b;i+=c)
#define SROF(i,a,b,c) for(Re int i = a;i >= b;i-=c)
#define CLR(i,a) memset(i,a,sizeof(i))
#define BR printf("--------------------\n")
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
#define int LL
const int MAXN = 5e5 + 5;
const int ha = 998244353;
inline int qpow(int a,int n){
int res = 1;
while(n){
if(n & 1) res = res*a%ha;
a = a*a%ha;
n >>= 1;
}
return res;
}
int a[MAXN],b[MAXN],c[MAXN],d[MAXN],r[MAXN],g[MAXN],f[MAXN],x[MAXN],n;
inline void NTT(int *A,int limit,int opt){
FOR(i,0,limit-1) if(i < r[i]) std::swap(A[i],A[r[i]]);
for(int mid = 1;mid < limit;mid <<= 1){
int W = qpow(3,(ha-1)/(mid<<1));
if(opt == -1) W = qpow(W,ha-2);
for(int j = 0;j < limit;j += (mid<<1)){
for(int k = 0,w = 1;k < mid;k++,w = w*W%ha){
int x = A[j+k],y = w*A[j+mid+k]%ha;
A[j+k] = (x+y)%ha;A[j+mid+k] = (x-y+ha)%ha;
}
}
}
if(opt == -1){
int inv = qpow(limit,ha-2);
FOR(i,0,limit-1) A[i] = A[i]*inv%ha;
}
}
inline void Inv(int *A,int *B,int n){
B[0] = qpow(A[0],ha-2);
int len;
for(len = 1;len < (n<<1);len <<= 1){
int d = len<<1;
FOR(i,0,len-1) a[i] = A[i],b[i] = B[i];
FOR(i,0,d-1) r[i] = (r[i>>1]>>1)|((i&1)?len:0);
NTT(a,d,1);NTT(b,d,1);
FOR(i,0,d-1) B[i] = (2-a[i]*b[i]%ha+ha)%ha*b[i]%ha;
NTT(B,d,-1);FOR(i,len,d-1) B[i] = 0;
}
FOR(i,0,len-1) a[i] = b[i] = 0;
FOR(i,n,len-1) B[i] = 0;
}
inline void Direv(int *A,int *B,int n){
FOR(i,1,n-1) B[i-1] = i*A[i]%ha;B[n-1] = 0;
}
inline void Inter(int *A,int *B,int n){
FOR(i,1,n-1) B[i] = A[i-1]*qpow(i,ha-2)%ha;B[0] = 0;
}
inline void getln(int *A,int *B,int n){
int *C = c,*D = d;
Direv(A,C,n);Inv(A,D,n);
int len = 0,limit = 1;
while(limit <= n) limit <<= 1,len++;
FOR(i,1,limit-1) r[i] = (r[i>>1]>>1)|((i&1)<<(len-1));
NTT(C,limit,1);NTT(D,limit,1);
FOR(i,0,limit-1) C[i] = C[i]*D[i]%ha;
NTT(C,limit,-1);Inter(C,B,n);
}
inline void Exp(int *A,int *B,int n){
B[0] = 1;int len;
for(len = 1;len < (n<<1);len <<= 1){
int d = len<<1;
getln(B,f,len);
FOR(i,0,len-1) f[i] = (A[i]+(i==0)-f[i]+ha)%ha;
FOR(i,0,d-1) r[i] = (r[i>>1]>>1)|((i&1)?len:0);
NTT(B,d,1);NTT(f,d,1);
FOR(i,0,d-1) B[i] = B[i]*f[i]%ha;
NTT(B,d,-1);
FOR(i,len,d-1) B[i] = 0;
}
FOR(i,0,len-1) f[i] = 0;FOR(i,n,len-1) B[i] = 0;
}
signed main(){
scanf("%lld",&n);
FOR(i,0,n-1) scanf("%lld",x+i);
int len;for(len=1;len<=n;len<<=1);Exp(x,g,len);
FOR(i,0,n-1) printf("%lld ",g[i]);
return 0;
} 
京公网安备 11010502036488号