专题·多项式基本操作【including 多项式逆元,多项式ln,牛顿迭代,多项式exp,多项式快速幂

初见安~被多项式毒瘤了这么久终于想起来整理了……QAQ

目录

一、多项式逆元

二、多项式ln

三、关于牛顿迭代式

1.泰勒(Taylor)展开式

2.牛顿迭代

四、多项式exp

五、多项式快速幂

1.指数较小时

2.指数较大时


一、多项式逆元

已知多项式A(x),求\small F(x)满足:

\small F(x)A(x)\equiv 1(mod~x^n)

\small mod~x^n的意思是:满足相乘后的多项式前n项除了常数项系数为1其余均为0】

我们假设已知:

\small F_0(x)A(x)\equiv 1(mod~x^{\frac{n}{2}})

那么由上文定义可得必有:

\small F(x)A(x)\equiv 1(mod ~x^{\frac{n}{2}})

所以两式相减:

\small F(x)-F_0(x)\equiv0(mod~x^{\frac{n}{2}})

两边同时平方:

\small F^2(x)-2F(x)F_0(x)+F_0^2(x)\equiv 0(mod~x^n)
这里似乎需要解释一下为什么模数也平方了:因为对于前n/2个都是0不用多说,而对于后面的n/2个,比如\small i \in [n/2,n),必有\small a_i=\sum a_ja_{i-j},这两项中必有一项是在n/2里为0,所以成立。

所以两边再同时根据最开头的两式乘上一个A(x)

\small F(x) \equiv 2F_0(x)-F_0^2(x)A(x)(mod~x^n)

接下来递归求解即可。

代码:

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

已知多项式A(x),求\small \dpi{120} \small B(x)满足:

\small B(x)\equiv ln{A(x)}(mod~x^n)

明显我们直接看这个ln就特别的不友好。有什么转化的方法呢——导数

导数有基本公式:\small ln'x=\frac{1}{x},若是复合函数整体求导的话外面再乘一个内层函数的导数即可。

所以我们对这个式子两边同时求导可得:

\small (lnA(x))'=\frac{A'(x)}{A(x)}

所以我们要求的东西就转化为了:

\small \int\frac{A'(x)}{A(x)}

上面求导,下面求逆元,整体求积分,复杂度为\small O(nlogn)

对于我这样的蒟蒻,其实第一眼的疑惑不是怎么求,而是:求导和积分怎么整啊【大雾】

下面简单讲一下这个东西【会者自略】
求导积分的定义建议去翻百度百科【我也是这么过来的……惨。
根据求导公式:\small (x^v)'=vx^{v-1}我们将一个多项式求导过后,会少一项。也就是说:对于\small f[v],求导后应为\small f[v+1]*(v+1)
【第一次看可能会有点混乱, 可以手动把这个多项式写下来,再对应求导后的结果来理解。】
同样的,在这个递推式的基础上求积分,就是导数的逆变换,乘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)展开式

不要问我为什么要讲这些东西。百度百科的时候被一群公式原理套娃我也很绝望啊

泰勒公式就是:

\small f(x)=\sum_{i=0}^\infty\frac{f^{(i)}(x_0)}{i!}(x-x_0)^i

其中x_0是可取任意一点,\small f^{(i)}就是f函数的i阶导。这个公式的准确含义及用途我也不是很清楚,但是很有用。

它有一些比较常用的展开,比如:【后面用不到的,看看就好了,叫做麦克劳林公式

\small e^x=\sum_{i=0}^\infty\frac{x^i}{i!}

2.牛顿迭代

已知\small g(x),求\small f(x),满足:

\small g(f(x))\equiv 0(mod~x^n)

这次是复合函数了呢。我们假设已知\small f_0(x)

\small g(f_0(x))\equiv0(mod~x^{n/2})

是不是就和泰勒展开有点点像了?我们把f函数当成变量带入Taylor公式可得:

\small g(f(x))=\frac{g(f_0(x))}{0!}+\frac{g'(f_0(x))}{1!}(f_(x)-f_0(x))+\frac{g''(f_0(x))}{2!}(f(x)-f_0(x))^2+...\\ ~~~~~~~~~~~~~~~=\frac{g(f_0(x))}{0!}+\frac{g'(f_0(x))}{1!}(f_(x)-f_0(x))

可以看出从第三项开始就都被省去了。为什么呢?因为都一定可以拆出一项为:\small (f(x)-f_0(x))^2.是不是很眼熟?就刚好是我们前面求多项式逆元的时候证明过的,这个东西满足:\small (f(x)-f_0(x))^2 \equiv 0(mod~x^n)。所以后面的项就都是0了。

所以就有:

\small f(x)=f_0(x)-\frac{g(f_0(x))}{g'(f_0(x))}【很可惜最后这一步是怎么化简过来的我也推不出来……】

最后化简出来的这个公式就是牛顿迭代式。其实到这里的时候,对于化简公式的问题,\small g(x)就只是个形式了。

一般的运用技巧是:将要求的式子转化成左边一坨,右边同余0的形式,然后将左边的部分全部视作\small g(f(x)),确认变量,带入最后的这个公式即可。

举个例子——求多项式exp的时候。

四、多项式exp

已知\small A(x),求\small B(x)满足:

\small B(x) \equiv e^{A(x)}(mod~x^n)

又是一个看起来很不友好的式子呢……直接求导作用不大,但我们学了对数,所以两边同时取对:

\small lnB(x)-A(x) \equiv 0(mod`x^n)

将左边那一坨都当成\small g(f(x)),即\small f(x)=B(x),带入牛顿迭代式就有:

\small B(x)=B_0(x)-\frac{lnB_0(x)-A(x)}{\frac{1}{B_0(x)}}

p.s:因为牛顿迭代中,下面部分求导,其中\small A(x)我们已知视作常数,所以:
\small (lnB_0(x)-A(x))'=ln'B_0(x)-0=\frac{1}{B_0(x)}

所以就有:

\small B(x)=B_0(x)(1-lnB_0(x)+A(x))

到这里其实我们就可以求解啦!!!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;
}

也就是说其实每一个部分的代码看起来都是很简单的,记住怎么推导的就好了。

五、多项式快速幂

已知\small A(x),求\small B(x)满足:

\small B(x)\equiv A^k(x)(mod~x^n)

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回来了。

这样快速幂的复杂度是\small O(nlogn*logk)

2.指数较大时

比如这个题:洛谷P5245 【模板】多项式快速幂

我们可以看到指数的范围是极其的大,明显不能用log的算法。【可能就我一个人有这种想法】也不能用费马小定理减小指数,因为这是多项式相乘,模\small x^n

怎么办呢——我们可以考虑:如何把指数给降下来。我们两边同时取对:

\small lnB(x) \equiv lnA^k(x) \equiv klnA(x)(mod~x^n)

这样一来我们就把k给降下来啦,并且很明显的是,k作为系数,是可以取余998244353的,我们快读的时候加一个取余的操作就解决k很大的问题了。现在要求\small B(x),两边再一起exp一下就好了。所以就有:

\small B(x)\equiv e^{klnA(x)}

直接套用我们前面的exp即可。就不放代码了。时间复杂度\small O(nlogn)

至此就是全部的内容啦!!!!!!!!!!!!!还有多项式开根,除法什么的以后学了再回来完善。

迎评:)
——End——

发布了158 篇原创文章 · 获赞 23 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_43326267/article/details/103855604
今日推荐