基本多项式

本文简单介绍一下基本多项式理论。

持续更新中。

因为是简单介绍,只说明做法,不给予严格证明,以及前人是如何想出的这个方法。

多项式乘法

多项式乘法与和卷积

\(A=\sum\limits_{i=0}^{n}a_ix^i,B=\sum\limits_{i=0}^{n}b_ix^i\)

显然有 \(A\times B=\sum\limits_{i=0}^n\sum\limits_{j=0}^na_ib_jx^{i+j}=\sum\limits_{d=0}^n\sum\limits_{i+j=d}^na_ib_jx^d\)

\(C=A\times B,C(d)=\sum\limits_{i+j=d}^na_ib_jx^d\)

这玩意就是和卷积,可见和卷积与多项式乘法有着千丝万缕的关系。

快速傅里叶变换

众所周知,一个 \(n-1\) 次多项式(函数)可以用 \(n\) 个不同的点唯一确定出来。

而如果考虑两个多项式 \(f,g\),当\(x=x_0\) 时,\(f\times g\ (x_0)=f(x_0)\times g(x_0)\)

这个很好理解。

所以点值表达乘积就是直接将相同横坐标的 \(y\) 乘。

那么如何将多项式 \(f\) 转成点值。

代入复数,用 \(n\) 次单位根做 \(x\)

\(x^n=1\)\(n\) 个复数解,记录以 \(x\) 轴正方向,逆时针第一个复根为 \(w\),所有单位根就是 \(w^0,w^1,\cdots,w^{n-1}\)

目标:求 \(\forall \ i,F(k)=\sum\limits_{j=0}^{n-1}a_j(w^i)^j\)

我们将次方奇数偶数分开考虑 \(F(k)=\sum\limits_{j=0}^{n}a_j(w^k)^j=\sum\limits_{j=0}^{n/2-1}a_{2j}(w^k)^{2j}+w^k\sum\limits_{j=0}^{n/2-1}a_{2j+1}(w^k)^{2j}\)

\(F(k)=\sum\limits_{j=0}^{n}a_j(w^k)^j=\sum\limits_{j=0}^{n/2-1}a_{2j}(w^{2k})^{j}+w^k\sum\limits_{j=0}^{n/2-1}a_{2j+1}(w^{2k})^{j}\)

由于 \((w^k)^2=(w^{k+n/2})^2\)\(w^n=1\))。

所以 \(F(k)\)\(F(k+n/2)\) 只差一点点,就是 \(w^k\) 项的正负号 (\(w^k=-w^{k+n/2}\))。

原本你要求 \(F(w^0),F(w^1),\cdots F(w^{n-1})\),现在只要求 \(F(w^0),F(w^1),\cdots F(w^{n/2-1})\)

范围直接缩小一倍。那么对于接下来的 \(F_0(w^{2k}),F_1(w^{2k})\)(系数是奇数(1)还是偶数(0))也可以递归下求。

递归效率缓慢,能够优化。

发现本质上每次将 \(k\),缩小一半的操作其实是想要最后把 \(w^0\) 算出,然后计算出别的。

可以如图按照一定顺序把 \(a_i\times (w^0)^i\) 按顺序排开,然后合并。

大概。。。

这个顺序通过观察 发现是二进制反过来 ,所以能快速求出。

代码解读一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const pie=acos(-1);
inline void fft(complex *a)
{
for(int i=0;i<lim;i++) if(i<rev[i]) swap(a[i],a[rev[i]]);
for(int mid=1;mid<lim;mid=mid<<1)//从下向上合并,mid 代表合并之前每块的长度
{
complex wn(cos(p/mid),sin(p/mid));// 合并后那个的w^k,模长都是1,用角度表示。
// 比如说 mid=lim/2 时,wn这个的角度就是 pie/(lim/2)=(2*pie)/lim
// 就是最初的单位根。
for(int r=mid<<1,j=0;j<lim;j+=r)//跳块,r就是合并后块长
{
complex w(1.0,0.0);
for(int k=0;k<mid;k++,w=w*wn) //w 就是你乘的 w^k (因为你更新得更新 w^0,w^k,w^2k,... 处的点值)
{
complex x=a[k+j],y=w*a[k+j+mid];
a[k+j]=x+y;
a[k+j+mid]=x-y;
}
}
}
}

一般人不用点值表达,所以必须要有逆傅里叶变换。

不难发现 由于范德蒙德矩阵在 \(A_{i,j}=w^{ij}\) 有着巨NB的性质,\(A^{-1}_{i,j}=w^{-ij}\)。 (前面那个 \(-1\) 的意思是逆矩阵)

所以只要把上文代码中的 wn(cos(p/mid),sin(p/mid)) 改成 wn(cos(-p/mid),sin(-p/mid)) 即可。

证明是个构造证明,我也不会从暴力手算 \(A_{i,j}\) 逆矩阵出发证明,而只会验证 \(A\times B=\epsilon\),\(B\) 即为你构造的那个逆矩阵。

快速数论变换

把单位根换成原根有相同性质。


再此贴出现在的Poly模板;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#include<iostream>
#include<cmath>
#include<vector>
#include<algorithm>
#include<assert.h>
using namespace std;
#define int long long
const int mod=998244353;
inline int qpow(int a,int b){
int ret=1;
while(b){
if(b&1) ret=ret*a%mod;
a=a*a%mod;b>>=1;
}return ret;
}
inline void up(int &x){
if(x>=mod) x-=mod;
}
int *rev[23],*wn[23];
inline void init(){
for(int mid=1,lg=0;mid<(1<<20);mid<<=1,lg++){
wn[lg]=new int[mid+1];
wn[lg][0]=1;
int w=qpow(3,(mod-1)/(mid<<1));
for(int k=1;k<mid;k++){
wn[lg][k]=wn[lg][k-1]*w%mod;
}
}
}
//O2 is Necessary!!!
struct poly{
#define rep(i,l,r) for(int i=l;i<=r;i++)
vector<int> a;

inline poly(){}
inline poly(vector<int> v){a=v;}
//construct a exactly x^n poly
inline poly(int n){a.resize(n+1);}
//return the exactly degree of the poly
inline int size() const{
return a.size()-1;
}
//reconstruct a exacly x^n poly
inline void resize(int lim){
a.resize(lim+1);
}
//the expected degree is x^{lim-1}
inline void ntt(int lim){
resize(lim-1);
int len=__lg(lim);
if(rev[len]==nullptr){
rev[len]=new int [lim+1];
rev[len][0]=0;
for(int i=1;i<lim;i++) {
rev[len][i]=(rev[len][i>>1]>>1)|((i&1)?(lim>>1):0);
}
}
for(int i=0;i<lim;i++) if(rev[len][i]<i) swap(a[rev[len][i]],a[i]);
for(int mid=1,lg=0;mid<lim;mid<<=1,lg++){
for(int j=0;j<lim;j+=(mid<<1)){
for(int k=0;k<mid;k++){
int x=a[j+k],y=a[j+k+mid]*wn[lg][k]%mod;
a[j+k]=(x+y);if(a[j+k]>=mod) a[j+k]-=mod;
a[j+k+mid]=(x-y);if(a[j+k+mid]<0) a[j+k+mid]+=mod;
}
}
}
}
//the expected degree is x^{lim-1}
inline void intt(int lim){
resize(lim-1);
reverse(a.begin()+1,a.end());
ntt(lim);
int inv=qpow(lim,mod-2);
for(int i=0;i<lim;i++) a[i]=a[i]*inv%mod;
}
inline int& operator [](int x){
return a[x];
}
inline poly operator +(const poly&b){
int sza=size(),szb=b.size();
int lim=max(sza,szb);
poly c(lim);
rep(i,0,lim) {
int x=0,y=0;
if(i<=sza) x=a[i];
if(i<=szb) y=b.a[i];
c[i]=(x+y),up(c[i]);
}
return c;
}
inline poly operator -(const poly&b){
int sza=size(),szb=b.size();
int lim=max(sza,szb);
poly c(lim);
rep(i,0,lim) {
int x=0,y=0;
if(i<=sza) x=a[i];
if(i<=szb) y=b.a[i];
c[i]=(x-y+mod),up(c[i]);
}
return c;
}
inline poly operator *(const poly&b){
poly c=b,d=(*this);
int len=c.size()+(d.size());
int lim=1;
while(lim<=len) lim<<=1;//lim -> x^{lim-1}
d.ntt(lim),c.ntt(lim);
rep(i,0,lim-1) c[i]=c[i]*d[i]%mod;//,cerr<<c[i]<<" ";cerr<<endl;
c.intt(lim);
c.resize(len);
return c;
}
inline poly operator *(int b){
poly c=(*this);
for(auto &x:c.a) x=(x*b)%mod;
return c;
}
//this is the first time You typing the Poly template,Ignore the constant factor!!!!!!
//mod x^lim
inline poly inv(int lim){
if(lim==1){
poly x(0);x[0]=qpow(a[0],mod-2);
return x;
}
poly c=(*this),ans;
c.resize(lim-1);
int l=(lim+1)/2;
poly inv=c.inv(l);
ans=(inv*2)-(inv*inv)*c;
ans.resize(lim-1);
return ans;
}
//mod x^lim
inline poly Sqrt(int lim){
if(lim==1){
poly ret(0);ret[0]=1;
return ret;
}
poly c=(*this);c.resize(lim-1);
int l=(lim+1)/2;
poly F0=c.Sqrt(l);
poly INV=F0.inv(lim-l+1);
//consider that iff (F0*F0-c)\bmod x^l=0,you can calculate till lim-l+1
auto ret=F0-(F0*F0-c)*INV*((mod+1)>>1);
ret.resize(lim-1);
return ret;
}
};

int32_t main(){
ios::sync_with_stdio(false);
init();

}

对于EGF的阐释性的理解。

注意我们说的OGF,EGF都是对于一个数列说的。

\(f\rightarrow F\)

\(f\) 的EGF定义为 \(\sum \frac{f_i}{i!}\cdot x^i\)

EGF 的好的性质主要体现在多项式每个系数除了对应的 \(i!\)

而CMD_BLK 说的二项卷积是对于 \(f,g\) 两个数列的卷积。

对于他们生成的函数/多项式,卷积依旧是经典的加法卷积。

如果考虑原本数列在卷积后变成什么了。

这么理解: \([x^i]1*[x^j]1=\binom{i+j}{i}\),把1变成原先每一项的含义即可。

讲道理prufer序列真的不是特别直观。


基本多项式
https://proton-z.github.io/2022/08/30/basic-poly/
作者
Imitators
发布于
2022年8月30日
许可协议