初见安~被多项式毒瘤了这么久终于想起来整理了……QAQ】
目录
一、多项式逆元
已知多项式,求满足:
。
【的意思是:满足相乘后的多项式前n项除了常数项系数为1其余均为0】
我们假设已知:
那么由上文定义可得必有:
所以两式相减:
两边同时平方:
这里似乎需要解释一下为什么模数也平方了:因为对于前n/2个都是0不用多说,而对于后面的n/2个,比如,必有,这两项中必有一项是在n/2里为0,所以成立。
所以两边再同时根据最开头的两式乘上一个:
接下来递归求解即可。
代码:
void get_inv(ll *a, ll *b, int n) {//求a的逆元放进b里
if(n == 1) {b[0] = pw(a[0], mod - 2); return;}//边界
get_inv(a, b, (n + 1) >> 1);//这里的n是元素个数,在这种写法下最好上取整
len = 1, l = 0;//因为len变化了,每次都要重新处理
while(len <= n + n) len <<= 1, l++;
for(int i = 1; i <= len; i++) r[i] = (r[i >> 1] >> 1) | ((i & 1) << l - 1);
for(int i = 0; i < n; i++) tmp[i] = a[i];//放进tmp里保证a不改变
for(int i = n; i <= len; i++) tmp[i] = 0;//一定要为0
NTT(tmp, 1), NTT(b, 1);
for(int i = 0; i <= len; i++) b[i] = b[i] * (1ll * 2 - tmp[i] * b[i] % mod + mod) % mod;
NTT(b, -1); for(int i = n; i <= len; i++) b[i] = 0;//这里也要记得清0,据定义只有前n项有效
}
二、多项式ln
已知多项式,求满足:
明显我们直接看这个ln就特别的不友好。有什么转化的方法呢——导数。
导数有基本公式:,若是复合函数整体求导的话外面再乘一个内层函数的导数即可。
所以我们对这个式子两边同时求导可得:
所以我们要求的东西就转化为了:
上面求导,下面求逆元,整体求积分,复杂度为。
对于我这样的蒟蒻,其实第一眼的疑惑不是怎么求,而是:求导和积分怎么整啊【大雾】
下面简单讲一下这个东西【会者自略】
求导积分的定义建议去翻百度百科【我也是这么过来的……惨。】
根据求导公式:我们将一个多项式求导过后,会少一项。也就是说:对于,求导后应为。
【第一次看可能会有点混乱, 可以手动把这个多项式写下来,再对应求导后的结果来理解。】
同样的,在这个递推式的基础上求积分,就是导数的逆变换,乘v+1的逆元即可。
下面上代码——
void deriv(ll *a, int n) {for(int i = 1; i < n; i++) a[i - 1] = i * a[i] % mod; a[n - 1] = 0;}
void integ(ll *a, int n) {for(int i = n - 1; i > 0; i--) a[i] = a[i - 1] * pw(i, mod - 2) % mod; a[0] = 0;}
void get_ln(ll *a, ll *b, int n) {
get_inv(a, b, n);//首先要求一次逆元,放到b里面
for(int i = 0; i < n; i++) tmp[i] = a[i];//放到tmp是为了应付多次调用ln的情况,尽量不改变a数组
for(int i = n; i <= len; i++) tmp[i] = 0;
deriv(tmp, n);//求导
NTT(tmp, 1), NTT(b, 1);
for(int i = 0; i <= len; i++) b[i] = b[i] * tmp[i] % mod;
NTT(b, -1); integ(b, n);//积分回来
}
三、关于牛顿迭代式
1.泰勒(Taylor)展开式
不要问我为什么要讲这些东西。百度百科的时候被一群公式原理套娃我也很绝望啊。
泰勒公式就是:
其中x_0是可取任意一点,就是f函数的i阶导。这个公式的准确含义及用途我也不是很清楚,但是很有用。
它有一些比较常用的展开,比如:【后面用不到的,看看就好了,叫做麦克劳林公式】
2.牛顿迭代
已知,求,满足:
这次是复合函数了呢。我们假设已知:
是不是就和泰勒展开有点点像了?我们把f函数当成变量带入Taylor公式可得:
可以看出从第三项开始就都被省去了。为什么呢?因为都一定可以拆出一项为:.是不是很眼熟?就刚好是我们前面求多项式逆元的时候证明过的,这个东西满足:。所以后面的项就都是0了。
所以就有:
【很可惜最后这一步是怎么化简过来的我也推不出来……】
最后化简出来的这个公式就是牛顿迭代式。其实到这里的时候,对于化简公式的问题,就只是个形式了。
一般的运用技巧是:将要求的式子转化成左边一坨,右边同余0的形式,然后将左边的部分全部视作,确认变量,带入最后的这个公式即可。
举个例子——求多项式exp的时候。
四、多项式exp
已知,求满足:
又是一个看起来很不友好的式子呢……直接求导作用不大,但我们学了对数,所以两边同时取对:
将左边那一坨都当成,即,带入牛顿迭代式就有:
p.s:因为牛顿迭代中,下面部分求导,其中我们已知视作常数,所以:
所以就有:
到这里其实我们就可以求解啦!!!ln套前面的对数,后面的括号内是加减法【注意1是常数项】,最后再NTT乘起来。
也就是说多项式exp包含了多项式ln,多项式ln包含了求导积分和多项式inv……【禁止套娃?雾】
上代码——
ll c[maxn];
void get_exp(ll *a, ll *b, int n) {
if(n == 1) {b[0] = 1; return;}//e的0次方是1
get_exp(a, b, n + 1 >> 1);
memset(c, 0, sizeof c);
get_ln(b, c, n);//c = ln(b)
c[0] = (1 - c[0] + a[0] + mod) % mod;//第0项因为有1这个常数所以单独处理
for(int i = 1; i < n; i++) c[i] = (a[i] - c[i] + mod) % mod;
NTT(c, 1), NTT(b, 1);
for(int i = 0; i <= len; i++) b[i] = b[i] * c[i] % mod;
NTT(b, -1);
for(int i = n; i <= len; i++) b[i] = 0;
}
也就是说其实每一个部分的代码看起来都是很简单的,记住怎么推导的就好了。
五、多项式快速幂
已知,求满足:
1.指数较小时
我们知道快速幂的写法,就是将指数二进制拆分,复杂度为一个log。多项式也可以这么整,每次相乘都NTT一下就可以了。
int n, m, k;
ll ta[maxn], tb[maxn], tc[maxn];
void mul(ll *a, ll *b, ll *c) {
for(int i = 0; i <= len; i++) ta[i] = a[i], tb[i] = b[i];//放到ta,tb,tc里面,不改变原数组
NTT(ta, 1), NTT(tb, 1);
for(int i = 0; i <= len; i++) tc[i] = ta[i] * tb[i] % mod;
NTT(tc, -1);
for(int i = 0; i < n; i++) c[i] = tc[i];
}
ll a[maxn], b[maxn];
signed main() {
//略
while(len <= n + n) len <<= 1, l++;//这两行是NTT的操作
for(int i = 1; i <= len; i++) r[i] = (r[i >> 1] >> 1) | ((i & 1) << l - 1);
while(k) {//形如快速幂
if(k & 1) mul(a, b, a);
mul(b, b, b);
k >>= 1;
}
//略
}
可能你会问,为什么每次乘过去过后都要NTT逆变换回来?很简单,因为我们要求的是前n项,如果一直不NTT回来的话,那么a数组的长度,也就是NTT时的len就会一直变长变长。根据NTT计算方法对半得到的原理,我们明显不能在点值表达啊上强行只看前n项。所以就要NTT回来了。
这样快速幂的复杂度是。
2.指数较大时
比如这个题:洛谷P5245 【模板】多项式快速幂
我们可以看到指数的范围是极其的大,明显不能用log的算法。【可能就我一个人有这种想法】也不能用费马小定理减小指数,因为这是多项式相乘,模。
怎么办呢——我们可以考虑:如何把指数给降下来。我们两边同时取对:
这样一来我们就把k给降下来啦,并且很明显的是,k作为系数,是可以取余998244353的,我们快读的时候加一个取余的操作就解决k很大的问题了。现在要求,两边再一起exp一下就好了。所以就有:
直接套用我们前面的exp即可。就不放代码了。时间复杂度。
至此就是全部的内容啦!!!!!!!!!!!!!还有多项式开根,除法什么的以后学了再回来完善。
迎评:)
——End——