多项式求逆
Procedure
多项式求逆是多项式模块中的一个重要操作(“操作”这个词看出如今多项式题是多么的工业化,犹如毒瘤8操作LCT),在做生成函数/多项式除法、多项式取模/多项式多点求值等中均有应用
对于一个n次多项式
,我们希望求出一个m-1次多项式
,满足
也就是说,
为
在模
意义下的逆(即
以后的项我们都不管了,前面的项乘起来只有常数项系数为1,其他系数都是0)
多项式求逆运用了倍增的思想
假设我们已经求出了
考虑如何求
移项
此时意味着
的0 ~ m/2-1次项的系数全都是0,那么将同余式两边平方,就变成0 ~ m-1次项的系数都为0
因此
展开
提一个
出来
此时容易看出
这样就求出了
模
意义下的乘法逆元
我们从m=1开始做,此时直接求常数项的乘法逆元即可,
然后可以推出m=2,4,8,16…
这样一直倍增下去,直到m>=我们所需要的次数,此时直接取前面我们需要的项,后面多出来的直接设为0即可(模掉了没有影响)
注意,由于我们求 是在模 意义下进行的,而不是 ,因此即使 的次数为m/2-1,此时的 的次数仍然要取m-1
分析时间复杂度
(常数相当大)
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次多项式
,m次多项式
(n>=m),我们希望求出一个n-m次多项式
,一个至多m-1次的多项式
,满足
,并且
小于
当
时,我们就可以直接对
求逆了
接下来的方法就比较高妙了:
我们引入多项式的反转操作,即将多项式的系数倒转过来
形式化的,对于n次多项式
,反转之后就是
对于上面的等式反转
此时相当于将x替换成1/x,等式仍然成立
右边分配x^n
观察各个多项式指数范围的变化。
原本的指数范围为
,现在仍然是
原本的指数范围为
,现在仍然是
原本的指数范围为
,现在仍然是
但余式有变化:
原本的指数范围为
,现在变成了
这下好了,如果我们对于等式两边同时模一个
…
保留次数小于等于n-m的项,
不变,
没了!
即
此时只需要对于 求出模意义下的逆,乘一下就得到 ,反转回来就是
带回原来的式子,一减就可以得出
分析时间复杂度
NTT、多项式求逆的复杂度均为
因此总的复杂度
常数更大了…
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;//得到余式
}