多项式求逆
Procedure
多项式求逆是多项式模块中的一个重要操作(“操作”这个词看出如今多项式题是多么的工业化,犹如毒瘤8操作LCT),在做生成函数/多项式除法、多项式取模/多项式多点求值等中均有应用
对于一个n次多项式
F
(
x
)
F(x)
F(x),我们希望求出一个m-1次多项式
G
(
x
)
G(x)
G(x),满足
F
(
x
)
G
(
x
)
≡
1
(
m
o
d
x
m
)
F(x)G(x)\equiv 1\pmod {x^m}
F(x)G(x)≡1(modxm)
也就是说,
G
(
x
)
G(x)
G(x)为
F
(
x
)
F(x)
F(x)在模
x
m
x^m
xm意义下的逆(即
x
m
x^m
xm以后的项我们都不管了,前面的项乘起来只有常数项系数为1,其他系数都是0)
多项式求逆运用了倍增的思想
假设我们已经求出了
B
(
x
)
,
B
(
x
)
F
(
x
)
≡
1
(
m
o
d
x
m
2
)
B(x),B(x)F(x)\equiv 1\pmod {x^{m\over 2}}
B(x),B(x)F(x)≡1(modx2m)
考虑如何求
G
(
x
)
G(x)
G(x)
移项
B
(
x
)
F
(
x
)
−
1
≡
0
(
m
o
d
x
m
2
)
B(x)F(x)-1\equiv 0\pmod {x^{m\over 2}}
B(x)F(x)−1≡0(modx2m)
此时意味着
B
(
x
)
F
(
x
)
−
1
B(x)F(x)-1
B(x)F(x)−1的0 ~ m/2-1次项的系数全都是0,那么将同余式两边平方,就变成0 ~ m-1次项的系数都为0
因此
(
B
(
x
)
F
(
x
)
−
1
)
2
≡
0
(
m
o
d
x
m
)
\left(B(x)F(x)-1\right)^2\equiv 0 \pmod {x^m}
(B(x)F(x)−1)2≡0(modxm)
展开
F
2
(
x
)
B
2
(
x
)
−
2
F
(
x
)
B
(
x
)
+
1
≡
0
(
m
o
d
x
m
)
F^2(x)B^2(x)-2F(x)B(x)+1\equiv 0\pmod {x^m}
F2(x)B2(x)−2F(x)B(x)+1≡0(modxm)
提一个
F
(
x
)
F(x)
F(x)出来
F
(
x
)
(
F
(
x
)
B
2
(
x
)
−
2
B
(
x
)
)
≡
−
1
(
m
o
d
x
m
)
F(x)\left(F(x)B^2(x)-2B(x)\right)\equiv -1\pmod {x^m}
F(x)(F(x)B2(x)−2B(x))≡−1(modxm)
此时容易看出
G
(
x
)
≡
2
B
(
x
)
−
F
(
x
)
B
2
(
x
)
(
m
o
d
x
m
)
G(x)\equiv 2B(x)-F(x)B^2(x) \pmod {x^m}
G(x)≡2B(x)−F(x)B2(x)(modxm)
这样就求出了
F
(
x
)
F(x)
F(x)模
x
m
x^m
xm意义下的乘法逆元
我们从m=1开始做,此时直接求常数项的乘法逆元即可,
然后可以推出m=2,4,8,16…
这样一直倍增下去,直到m>=我们所需要的次数,此时直接取前面我们需要的项,后面多出来的直接设为0即可(模掉了没有影响)
注意,由于我们求 G ( x ) G(x) G(x)是在模 x m x^m xm意义下进行的,而不是 x m 2 x^{m\over 2} x2m,因此即使 B ( x ) B(x) B(x)的次数为m/2-1,此时的 F ( x ) F(x) F(x)的次数仍然要取m-1
分析时间复杂度
T
(
n
)
=
T
(
n
/
2
)
+
O
(
n
log
n
)
=
O
(
n
log
n
)
T(n)=T(n/2)+O(n\log n)=O(n\log n)
T(n)=T(n/2)+O(nlogn)=O(nlogn)(常数相当大)
Code
void make(int l,LL *a,LL *b)//l为我们需要的次数,a为待求逆多项式,用b来存储结果
{
b[0]=ksm(a[0],mo-2);//求常数项逆元
for(int m=1,t=2,num=4,cnt=2;m<l;m=t,t=num,num<<=1,cnt++)
//由于乘法有平方操作,因此NTT的范围需要开到2倍。
//t为当前的模数次数,m=t/2
{
prp(num,cnt);//预处理单位根、反位等
fo(i,0,m-1) c[i]=a[i],d[i]=b[i];
fo(i,m,t-1) c[i]=a[i];
fo(i,t,num-1) c[i]=0;
NTT(c,0,num),NTT(b,0,num);
fo(i,0,num-1) b[i]=b[i]*b[i]%mo*c[i]%mo;
NTT(b,1,num);
fo(i,0,t-1) b[i]=((LL)2*d[i]-b[i]+mo)%mo;
fo(i,t,num-1) b[i]=0;
}
}
多项式除法(多项式取模)
Procedure
多项式除法/取模也是多项式模块中的一个重要操作,在做生成函数/多项式多点求值等中均有应用。。。
对于一个n次多项式
F
(
x
)
F(x)
F(x),m次多项式
G
(
x
)
G(x)
G(x)(n>=m),我们希望求出一个n-m次多项式
H
(
x
)
H(x)
H(x),一个至多m-1次的多项式
R
(
x
)
R(x)
R(x),满足
F
(
x
)
=
G
(
x
)
H
(
x
)
+
R
(
x
)
F(x)=G(x)H(x)+R(x)
F(x)=G(x)H(x)+R(x),并且
R
(
x
)
R(x)
R(x)小于
G
(
x
)
G(x)
G(x)
当
R
(
x
)
≡
0
(
m
o
d
x
n
−
m
+
1
)
R(x)\equiv 0\pmod {x^{n-m+1}}
R(x)≡0(modxn−m+1)时,我们就可以直接对
G
(
x
)
G(x)
G(x)求逆了
接下来的方法就比较高妙了:
我们引入多项式的反转操作,即将多项式的系数倒转过来
形式化的,对于n次多项式
F
(
x
)
F(x)
F(x),反转之后就是
x
n
F
(
1
x
)
x^nF\left({1\over x}\right)
xnF(x1)
对于上面的等式反转
x
n
F
(
1
x
)
=
x
n
(
G
(
1
x
)
H
(
1
x
)
+
R
(
1
x
)
)
x^nF\left({1\over x}\right)=x^n(G\left({1\over x}\right)H\left({1\over x}\right)+R\left({1\over x}\right))
xnF(x1)=xn(G(x1)H(x1)+R(x1))
此时相当于将x替换成1/x,等式仍然成立
右边分配x^n
x
n
F
(
1
x
)
=
x
m
G
(
1
x
)
x
n
−
m
H
(
1
x
)
+
x
n
R
(
1
x
)
x^nF\left({1\over x}\right)=x^mG\left({1\over x}\right)x^{n-m}H\left({1\over x}\right)+x^nR\left({1\over x}\right)
xnF(x1)=xmG(x1)xn−mH(x1)+xnR(x1)
观察各个多项式指数范围的变化。
F
(
x
)
F(x)
F(x)原本的指数范围为
[
0
,
n
]
[0,n]
[0,n],现在仍然是
[
0
,
n
]
[0,n]
[0,n]
G
(
x
)
G(x)
G(x)原本的指数范围为
[
0
,
m
]
[0,m]
[0,m],现在仍然是
[
0
,
m
]
[0,m]
[0,m]
H
(
x
)
H(x)
H(x)原本的指数范围为
[
0
,
n
−
m
]
[0,n-m]
[0,n−m],现在仍然是
[
0
,
n
−
m
]
[0,n-m]
[0,n−m]
但余式有变化:
R
(
x
)
R(x)
R(x)原本的指数范围为
[
0
,
m
−
1
]
[0,m-1]
[0,m−1],现在变成了
[
n
−
m
+
1
,
n
]
[n-m+1,n]
[n−m+1,n]
这下好了,如果我们对于等式两边同时模一个
x
n
−
m
+
1
x^{n-m+1}
xn−m+1…
F
(
x
)
,
G
(
x
)
F(x),G(x)
F(x),G(x)保留次数小于等于n-m的项,
H
(
x
)
H(x)
H(x)不变,
R
(
x
)
R(x)
R(x)没了!
即
x
n
F
(
1
x
)
≡
x
m
G
(
1
x
)
x
n
−
m
H
(
1
x
)
(
m
o
d
x
n
−
m
+
1
)
x^nF\left({1\over x}\right)\equiv x^mG\left({1\over x}\right)x^{n-m}H\left({1\over x}\right)\pmod {x^{n-m+1}}
xnF(x1)≡xmG(x1)xn−mH(x1)(modxn−m+1)
此时只需要对于 G ( 1 / x ) G(1/x) G(1/x)求出模意义下的逆,乘一下就得到 H ( 1 / x ) H(1/x) H(1/x),反转回来就是 H ( x ) H(x) H(x)
H ( x ) H(x) H(x)带回原来的式子,一减就可以得出 R ( x ) R(x) R(x)
分析时间复杂度
NTT、多项式求逆的复杂度均为
O
(
n
log
n
)
O(n\log n)
O(nlogn)
因此总的复杂度
O
(
n
log
n
)
O(n\log n)
O(nlogn)
常数更大了…
Code
void rev(int num,LL *a,LL *b)//反转操作
{
fo(i,0,num-1) b[i]=a[num-i-1];
}
void div(LL *a,LL *b,LL *d,LL *r)
{
rev(m+1,b,r);
make(n-m+1,r,d);//求出除数的逆
int num=cf[l2[n]]*2;
prp(num);
fo(i,0,n) f[i]=a[n-i];
fo(i,n-m+1,num-1) f[i]=0,d[i]=0;
NTT(f,0,num),NTT(d,0,num);
fo(i,0,num-1) d[i]=d[i]*f[i]%mo;
NTT(d,1,num);
fo(i,n-m+1,num-1) d[i]=0;
fo(i,0,(n-m)>>1) swap(d[i],d[n-m-i]);//反转回来,得到商
fo(i,0,num-1) r[i]=d[i],f[i]=b[i];
num=cf[l2[n+1]];
prp(num);
NTT(f,0,num),NTT(r,0,num);
fo(i,0,num-1) r[i]=r[i]*f[i]%mo;
NTT(r,1,num);
fo(i,0,num-1) r[i]=(a[i]-r[i]+mo)%mo;//得到余式
}