多项式的基础操作(逆元/除法/取模/对数ln/开根sqrt/指数exp/快速幂)带模板+luogu全套例题

由于本蒟蒻比较差,每一份代码都是交 A C AC AC了才来的,所以不用怀疑它的真实行,当然代码和模板都不是luogu上面加强版,所以常数项恰好都是 1 1 1,温馨提醒:不要乱用
在这里插入图片描述在这里插入图片描述

多项式的逆元

理论推导

知 识 储 备 知识储备

( a + b ) % c = ( a % c + b % c ) % c (a+b)\%c=(a\%c+b\%c)\%c (a+b)%c=(a%c+b%c)%c
( a − b ) % c = ( a % c − b % c + c ) % c (a-b)\%c=(a\%c-b\%c+c)\%c (ab)%c=(a%cb%c+c)%c
( a ∗ b ) % c = ( a % c ) ∗ ( b % c ) % c (a*b)\%c=(a\%c)*(b\%c)\%c (ab)%c=(a%c)(b%c)%c
也就是说如果你的算法只使用了加法、减法和乘法,你可以在所有的中间步骤取模,这和只对答案取模是等效的

同余的一些运算
a ≡ b ( m o d   m ) , c ≡ d ( m o d   m ) = > a ∗ c ≡ b ∗ d ( m o d   m ) a\equiv b ( \mathrm{mod\:} m ),c\equiv d ( \mathrm{mod\:} m )=>a*c \equiv b*d ( \mathrm{mod\:} m ) ab(modm),cd(modm)=>acbd(modm)
a ≡ b ( m o d   m ) , c ≡ d ( m o d   m ) = > a ± c ≡ b ± d ( m o d   m ) a\equiv b ( \mathrm{mod\:} m ),c\equiv d ( \mathrm{mod\:} m )=>a±c \equiv b±d ( \mathrm{mod\:} m ) ab(modm),cd(modm)=>a±cb±d(modm)
a ≡ b ( m o d   m ) = > k a ≡ k b ( m o d   k m ) a\equiv b ( \mathrm{mod\:} m )=>ka \equiv kb ( \mathrm{mod\:} km ) ab(modm)=>kakb(modkm)
a c ≡ b c ( m o d   m ) = > a ≡ b ( m o d   m ( c , m ) ) ac\equiv bc ( \mathrm{mod\:} m )=>a \equiv b ( \mathrm{mod\:} \frac{m}{(c,m)} ) acbc(modm)=>ab(mod(c,m)m)


接下来进入正题:
设给定的多项式为 A ( x ) A(x) A(x),求 A − 1 ( x ) A^{-1}(x) A1(x)满足:👇
A ( x ) ∗ A − 1 ( x ) ≡ 1 ( m o d   x n ) A(x)*A^{-1}(x)\equiv1(mod\ x^n) A(x)A1(x)1(mod xn)
m o d   x n mod\ x^n mod xn的意思即为舍去多项式里次数 ≥ n \ge n n的项,只保留次数 ∈ [ 0 , n − 1 ] ∈[0,n-1] [0,n1]的项
首先如果只有常数项,我们可以直接快速幂算出逆元(利用费马小定理

假设已知 A ( x ) A(x) A(x)在关于 m o d   x ⌈ n 2 ⌉ mod\ x^{\lceil\frac{n}{2}\rceil} mod x2n意义下的逆元 B 0 ( x ) B_0(x) B0(x),满足
A ( x ) ∗ B 0 ( x ) ≡ 1 ( m o d   x ⌈ n 2 ⌉ ) A(x)*B_0(x)\equiv1(mod\ x^{\lceil\frac{n}{2}\rceil}) A(x)B0(x)1(mod x2n)
记要求的答案为 B ( x ) B(x) B(x),有
A ( x ) ∗ B ( x ) ≡ 1 ( m o d   x n ) A(x)*B(x)\equiv1(mod\ x^n) A(x)B(x)1(mod xn)
两式相减,则有
A ( x ) ∗ ( B ( x ) − B 0 ( x ) ) ≡ 0 ( m o d   x ⌈ n 2 ⌉ ) A(x)*(B(x)-B_0(x))\equiv0(mod\ x^{\lceil\frac{n}{2}\rceil}) A(x)(B(x)B0(x))0(mod x2n)
因为 A ( x ) A(x) A(x)肯定不为 0 0 0,所以上述式子要成立的话只能寄希望于 ( B ( x ) − B 0 ( x ) ) (B(x)-B_0(x)) (B(x)B0(x)),所以化简得
B ( x ) − B 0 ( x ) ≡ 0 ( m o d   x ⌈ n 2 ⌉ ) B(x)-B_0(x)\equiv0(mod\ x^{\lceil\frac{n}{2}\rceil}) B(x)B0(x)0(mod x2n)
我们怎么把 m o d   x ⌈ n 2 ⌉ mod\ x^{\lceil\frac{n}{2}\rceil} mod x2n升级到 m o d   x n mod\ x^n mod xn很简单 将式子平方一下
在这里插入图片描述可以得到
( B ( x ) − B 0 ( x ) ) 2 ≡ 0 ( m o d   x n ) (B(x)-B_0(x))^2\equiv0(mod\ x^n) (B(x)B0(x))20(mod xn)
展开这个式子:
B 2 ( x ) + B 0 2 ( x ) − 2 ∗ B ( x ) ∗ B 0 ( x ) ≡ 0 ( m o d   x n ) B^2(x)+B_0^2(x)-2*B(x)*B_0(x)\equiv0(mod\ x^n) B2(x)+B02(x)2B(x)B0(x)0(mod xn)
我们来证明一下为什么可以 m o d   x n mod\ x^n mod xn,思考多项式乘法的过程, c i , a i , b i c_i,a_i,b_i ci,ai,bi都表示各自多项式中 x i x^i xi的系数
c i = ∑ j = 0 i a j ∗ b i − j … … ① c_i=\sum_{j=0}^{i}a_j*b_{i-j}……① ci=j=0iajbij
B ( x ) − B 0 ( x ) ≡ 0 ( m o d   x ⌈ n 2 ⌉ ) … … ② B(x)-B_0(x)\equiv0(mod\ x^{\lceil\frac{n}{2}\rceil})……② B(x)B0(x)0(mod x2n)
首先这两个式子的正确性不用证明了吧!我们把 B ( x ) − B 0 ( x ) B(x)-B_0(x) B(x)B0(x)看成一个整体 C ( x ) C(x) C(x),虽然不能保证 B , B 0 B,B_0 B,B0 m o d   x ⌈ n 2 ⌉ mod\ x^{\lceil\frac{n}{2}\rceil} mod x2n意义下的系数都为 0 0 0,但是因为 ② ② 我们可知 C ( x ) C(x) C(x) m o d   x ⌈ n 2 ⌉ mod\ x^{\lceil\frac{n}{2}\rceil} mod x2n意义下的系数都为 0 0 0,那么平方就转换成了 C ( x ) ∗ C ( x ) C(x)*C(x) C(x)C(x),套用 ① ①
a n s i = ∑ j = 0 i c j ∗ c i − j ans_i=\sum_{j=0}^ic_j*c_{i-j} ansi=j=0icjcij
发现在 < n <n <n的项至少是由其中一个 C ( x ) C(x) C(x)中的 < ⌈ n 2 ⌉ <\lceil\frac{n}{2}\rceil <2n项乘以其他项的结果,但是当 = n =n =n时,是会遇到 c ⌈ n 2 ⌉ ∗ c ⌈ n 2 ⌉ c_{\lceil\frac{n}{2}\rceil}*c_{\lceil\frac{n}{2}\rceil} c2nc2n,结合上面的公式 B ( x ) − B 0 ( x ) ≡ 0 ( m o d   x ⌈ n 2 ⌉ ) B(x)-B_0(x)\equiv0(mod\ x^{\lceil\frac{n}{2}\rceil}) B(x)B0(x)0(mod x2n) x ⌈ n 2 ⌉ x^{\lceil\frac{n}{2}\rceil} x2n是被舍掉了的,无法保证一定为 0 0 0
所以平方后的式子 [ 0 , n − 1 ] [0,n-1] [0,n1]次项都是 0 0 0,但是 n n n次项不确定,就成为一条分水岭,故把取模的界点设在 n n n可以砍掉,而不设在 n − 1 n-1 n1或者 n + 1 n+1 n+1等位置


大家最关心的时间复杂度,因为不停的 / 2 /2 /2用递归,就是 l o g log log级别,所以 O ( n l o g n ) O(nlogn) O(nlogn)左右皆大欢喜

模板

void solve ( int deg, int *b ) {
	if ( deg == 1 ) {
		b[0] = qkpow ( a[0], mod - 2 );
		return;
	}
	solve ( ( deg + 1 ) >> 1, b );
	len = 1;
	l = 0;
	while ( len < deg * 2 - 1 ) {
		len <<= 1;
		l ++;
	}
	inv = qkpow ( len, mod - 2 );
	for ( int i = 0;i < len;i ++ )
		r[i] = ( r[i >> 1] >> 1 ) | ( ( i & 1 ) << ( l - 1 ) );
	for ( int i = 0;i < deg;i ++ )
		c[i] = a[i];
	for ( int i = deg;i < n;i ++ )
		c[i] = 0;
	NTT ( c, 1 );
	NTT ( b, 1 );
	for ( int i = 0;i < len;i ++ )
		b[i] = ( 2 - b[i] * c[i] % mod + mod ) % mod * b[i] % mod; 
	NTT ( b, -1 );
	for ( int i = deg;i < n;i ++ )
		b[i] = 0;
}

例题:[luogu P4238]【模板】多项式乘法逆

题目

给定一个多项式 F ( x ) F(x) F(x) ,请求出一个多项式 G ( x ) G(x) G(x), 满足 F ( x ) ∗ G ( x ) ≡ 1 ( m o d   x n ) F(x) * G(x) \equiv 1 ( \mathrm{mod\:} x^n ) F(x)G(x)1(modxn)系数对 998244353 998244353 998244353 取模。

输入格式
首先输入一个整数 n n n, 表示输入多项式的项数
接着输入 n n n 个整数,第 i i i 个整数 a i a_i ai代表 F ( x ) F(x) F(x)次数为 i − 1 i-1 i1的项的系数。

输出格式
输出 n n n个数字,第 i i i个整数 b i b_i bi,代表 G ( x ) G(x) G(x)次数为 i − 1 i-1 i1 的项的系数。 保证有解。

输入输出样例
输入
5
1 6 3 4 9
输出
1 998244347 33 998244169 1020
说明/提示
1 ≤ n ≤ 1 0 5 , 0 ≤ a i ≤ 1 0 9 1 \leq n \leq 10^5, 0 \leq a_i \leq 10^9 1n105,0ai109

code

998244353 998244353 998244353的原根是 3 3 3,所以就直接用了
在这里插入图片描述

#include <cstdio>
#include <iostream>
using namespace std;
#define mod 998244353
#define MAXN 300005
#define int long long
int n, l, pi, ni, len, inv;
int a[MAXN], b[MAXN], c[MAXN], r[MAXN];

int qkpow ( int x, int y ) {
	int ans = 1;
	while ( y ) {
		if ( y & 1 )
			ans = ans * x % mod;
		x = x * x % mod;
		y >>= 1;
	}
	return ans;
}

void NTT ( int *c, int f ) {
	for ( int i = 0;i < len;i ++ )
		if ( i < r[i] )
			swap ( c[i], c[r[i]] );
	for ( int i = 1;i < len;i <<= 1 ) {
		int omega = qkpow ( f == 1 ? pi : ni, ( mod - 1 ) / ( i << 1 ) );
		for ( int j = 0;j < len;j += ( i << 1 ) ) {
			int w = 1;
			for ( int k = 0;k < i;k ++, w = w * omega % mod ) {
				int x = c[k + j], y = w * c[k + j + i] % mod;
				c[k + j] = ( x + y ) % mod;
				c[k + j + i] = ( x - y + mod ) % mod;
			}
		}
	}
	if ( f == -1 )
		for ( int i = 0;i < len;i ++ )
			c[i] = c[i] * inv % mod;
}

void polyinv ( int deg, int *a, int *b ) {//求多项式a的逆元b 
	if ( deg == 1 ) {
		b[0] = qkpow ( a[0], mod - 2 );
		return;
	}
	polyinv ( ( deg + 1 ) >> 1, a, b );
	len = 1;
	l = 0;
	while ( len < deg * 2 - 1 ) {
		len <<= 1;
		l ++;
	}
	inv = qkpow ( len, mod - 2 );
	for ( int i = 0;i < len;i ++ )
		r[i] = ( r[i >> 1] >> 1 ) | ( ( i & 1 ) << ( l - 1 ) );
	for ( int i = 0;i < deg;i ++ )
		c[i] = a[i];
	for ( int i = deg;i < n;i ++ )
		c[i] = 0;
	NTT ( c, 1 );
	NTT ( b, 1 );
	for ( int i = 0;i < len;i ++ )
		b[i] = ( 2 - b[i] * c[i] % mod + mod ) % mod * b[i] % mod; 
	NTT ( b, -1 );
	for ( int i = deg;i < n;i ++ )
		b[i] = 0;
}

signed main() {
	pi = 3;
	ni = mod / pi + 1;
	scanf ( "%lld", &n );
	for ( int i = 0;i < n;i ++ )
		scanf ( "%lld", &a[i] );
	polyinv ( n, a, b );
	for ( int i = 0;i < n;i ++ )
		printf ( "%lld ", b[i] );
	return 0;
}

多项式的除法/取模

理论推导

给定一个 n − 1 n-1 n1次的多项式 A ( x ) A(x) A(x) m − 1 m-1 m1次的多项式 B ( x ) B(x) B(x),求 D ( x ) , R ( x ) D(x),R(x) D(x),R(x)满足
A ( x ) = D ( x ) ∗ B ( x ) + R ( x ) … … ① A(x)=D(x)*B(x)+R(x)……① A(x)=D(x)B(x)+R(x)
其中自然 D ( x ) D(x) D(x)的最高次为 n − m n-m nm R ( x ) R(x) R(x)最高次 < m − 1 <m-1 <m1
或者 A ( x ) ≡ R ( x ) ( m o d   B ( x ) ) A(x)\equiv R(x)(mod\ B(x)) A(x)R(x)(mod B(x))
因为有余数 R ( x ) R(x) R(x)难以对其进行处理,所以考虑去掉这个玩意儿带来的不良影响
定义反转操作,即将各项系数反转 dalao让我们这么搞,只能跟着
在这里插入图片描述
A R ( x ) = x n − 1 A ( 1 x ) = ∑ i = 0 n − 1 a n − i − 1 x i … … ② A^R(x)=x^{n-1}A(\frac{1}{x})=\sum_{i=0}^{n-1}a_{n-i-1}x^i……② AR(x)=xn1A(x1)=i=0n1ani1xi
证明很简单,把中间的那一步 A ( 1 x ) A(\frac{1}{x}) A(x1)展开再与外面的 x n − 1 x^{n-1} xn1相乘,你会发现是一样的,好下一步
1 x \frac{1}{x} x1代入 ① ① ,再同乘各多项式的 x n − 1 x^{n-1} xn1得到
x n − 1 A ( 1 x ) = x n − 1 ∗ D ( 1 x ) ∗ B ( 1 x ) + x n − 1 ∗ R ( 1 x ) x^{n-1}A(\frac{1}{x})=x^{n-1}*D(\frac{1}{x})*B(\frac{1}{x})+x^{n-1}*R(\frac{1}{x}) xn1A(x1)=xn1D(x1)B(x1)+xn1R(x1)
我们把 x n − 1 x^{n-1} xn1拆分成 x n − m ∗ x m − 1 x^{n-m}*x^{m-1} xnmxm1,因为它们分别对应了 D ( x ) , B ( x ) D(x),B(x) D(x),B(x)的最高次,再把 x n − 1 x^{n-1} xn1拆分成 x n − m + 1 ∗ x m − 2 x^{n-m+1}*x^{m-2} xnm+1xm2,因为 x m − 2 x^{m-2} xm2 R ( x ) R(x) R(x)能达到的最高次,我们就强买强卖要求 R ( x ) R(x) R(x)达到最高次,大不了系数为0,即
x n − 1 A ( 1 x ) = x n − m D ( 1 x ) ∗ x m − 1 B ( 1 x ) + x n − m + 1 ∗ x m − 2 R ( 1 x ) x^{n-1}A(\frac{1}{x})=x^{n-m}D(\frac{1}{x})*x^{m-1}B(\frac{1}{x})+x^{n-m+1}*x^{m-2}R(\frac{1}{x}) xn1A(x1)=xnmD(x1)xm1B(x1)+xnm+1xm2R(x1)
② ② 可转换为
A R ( x ) = D R ( x ) B R ( x ) + x n − m + 1 R R ( x ) A^R(x)=D^R(x)B^R(x)+x^{n-m+1}R^R(x) AR(x)=DR(x)BR(x)+xnm+1RR(x)
由于 D R ( x ) D^R(x) DR(x) n − m n-m nm次的,所以在 m o d   x n − m + 1 mod\ x^{n-m+1} mod xnm+1意义下,就可以得到 A R ( x ) ≡ D R ( x ) ∗ B R ( x )   ( m o d   x n − m + 1 ) A^R(x)\equiv D^R(x)*B^R(x)\ (mod\ x^{n-m+1}) AR(x)DR(x)BR(x) (mod xnm+1)猥琐目的达到
所以我们可以用多项式求逆得到 D R ( x ) D^R(x) DR(x),反转即为 D ( x ) D(x) D(x),然后再带入求 R ( x ) R(x) R(x)即可

所以上面的操作真的是妙极了!!!

多项式牛顿迭代法

有一个关于多项式 f ( x ) f(x) f(x)的方程 g ( f ( x ) ) = 0 g(f(x))=0 g(f(x))=0
假设已经求出 f ( x ) f(x) f(x)的前 n n n f 0 ( x ) f_0(x) f0(x),则有
f ( x ) ≡ f 0 ( x )   ( m o d   x n ) f(x)\equiv f_0(x)\ (mod\ x^n) f(x)f0(x) (mod xn)
g ( f 0 ( x ) ) ≡ 0   ( m o d   x n ) g(f_0(x))\equiv 0\ (mod\ x^n) g(f0(x))0 (mod xn)
g ( f ( x ) ) g(f(x)) g(f(x)) f ( x ) f(x) f(x)上进行泰勒展开
g ( f ( x ) ) = g ( f 0 ( x ) ) + g ′ ( f 0 ( x ) ) 1 ! ( f ( x ) − f 0 ( x ) ) 1 + g ′ ′ ( f 0 ( x ) ) 2 ! ( f ( x ) − f 0 ( x ) ) 2 + … … g(f(x))=g(f_0(x))+\frac{g'(f_0(x))}{1!}(f(x)-f_0(x))^1+\frac{g''(f_0(x))}{2!}(f(x)-f_0(x))^2+…… g(f(x))=g(f0(x))+1!g(f0(x))(f(x)f0(x))1+2!g(f0(x))(f(x)f0(x))2+
与上面内容结合 f ( x ) − f 0 ( x ) f(x)-f_0(x) f(x)f0(x)的前 n n n项系数为 0 0 0,于是 g ( f ( x ) ) ≡ g ( f 0 ( x ) ) + g ′ ( f 0 ( x ) ) ( f ( x ) − f 0 ( x ) ) ≡ 0   ( m o d   x 2 n ) g(f(x))\equiv g(f_0(x))+g'(f_0(x))(f(x)-f_0(x))\equiv 0\ (mod\ x^{2n}) g(f(x))g(f0(x))+g(f0(x))(f(x)f0(x))0 (mod x2n)

为什么从第三项开始就可以省略了呢??里面有清晰的证明

f ( x ) ≡ f 0 ( x ) − g ( f 0 ( x ) ) g ′ ( f 0 ( x ) )   ( m o d   x 2 n ) f(x)\equiv f_0(x)-\frac{g(f_0(x))}{g'(f_0(x))}\ (mod\ x^{2n}) f(x)f0(x)g(f0(x))g(f0(x)) (mod x2n)


g ( f ( x ) ) ≡ l n f ( x ) − A ( x ) g(f(x))\equiv lnf(x)-A(x) g(f(x))lnf(x)A(x)
在这里插入图片描述

这个函数的零点为 e A ( x ) e^A(x) eA(x),即 A ( x ) A(x) A(x) e x p exp exp
简单来说,多项式牛顿迭代法就是用来解关于多项式的方程的
——摘自某dalao

用这种方法也可以推多项式求逆,但是更多的是后面的一些基础操作,先卖个关子
在这里插入图片描述

模板

void polydiv ( int n, int m,int *d, int *a, int *b, int *r ) {
	for ( int i = 0;i < n;i ++ )//进行反转操作 
		A[i] = a[n - i - 1];
	for ( int i = 0;i < m;i ++ )
		B[i] = b[m - i - 1];
	polyinv ( n - m + 1, B, Binv );//根据推导的公式求出B在mod x^(n-m+1)意义下的逆
	len = 1;
	l = 0;
	while ( len < ( n << 1 ) - m ) {
		len <<= 1;
		l ++;
	}
	for ( int i = 0;i < len;i ++ )
		rev[i] = ( rev[i >> 1] >> 1 ) | ( ( i & 1 ) << ( l - 1 ) );
	NTT ( A, 1, len );
	NTT ( Binv, 1, len );
	for ( int i = 0;i < len;i ++ )//求反转的商D 
		d[i] = A[i] * Binv[i] % mod;
	NTT ( d, -1, len ); 
	for ( int i = 0, j = n - m;i < j;i ++, j -- )//把商反转成正常 
		swap ( d[i], d[j] );
	//接下来搞r 
	memset ( B, 0, sizeof ( B ) );
	memset ( Binv, 0, sizeof ( Binv ) );
	memset ( rev, 0, sizeof ( rev ) );
	len = 1;
	l = 0;
	while ( len < n ) {
		l ++;
		len <<= 1;
	}
	for ( int i = 0;i < len;i ++ )
		rev[i] = ( rev[i >> 1] >> 1 ) | ( ( i & 1 ) << ( l - 1 ) ); 
	for ( int i = 0;i < m;i ++ )
		B[i] = b[i];
	for ( int i = 0;i < n - m + 1;i ++ )
		Binv[i] = d[i];
	NTT ( B, 1, len );
	NTT ( Binv, 1, len );
	for ( int i = 0;i < len;i ++ )
		r[i] = B[i] * Binv[i] % mod;
	NTT ( r, -1, len );
	for ( int i = 0;i < m;i ++ )
		r[i] = ( a[i] - r[i] + mod ) % mod;
}

例题:[luoguP4512]【模板】多项式除法

题目

给定一个 n n n 次多项式 F ( x ) F(x) F(x) 和一个 m m m 次多项式 G ( x ) G(x) G(x) ,请求出多项式 Q ( x ) , R ( x ) Q(x), R(x) Q(x),R(x),满足以下条件:
Q ( x ) Q(x) Q(x) 次数为 n − m n−m nm R ( x ) R(x) R(x) 次数小于 m m m
F ( x ) = Q ( x ) ∗ G ( x ) + R ( x ) F(x) = Q(x) * G(x) + R(x) F(x)=Q(x)G(x)+R(x)
所有的运算在模 998244353 998244353 998244353 意义下进行。

输入格式
第一行两个整数 n n n m m m,意义如上。
第二行 n + 1 n+1 n+1个整数,从低到高表示 F ( x ) F(x) F(x) 的各个系数。
第三行 m m m 个整数,从低到高表示 G ( x ) G(x) G(x) 的各个系数。

输出格式
第一行 n − m + 1 n-m+1 nm+1 个整数,从低到高表示 Q ( x ) Q(x) Q(x) 的各个系数。
第二行 m m m 个整数,从低到高表示 R ( x ) R(x) R(x) 的各个系数。
如果 R ( x ) R(x) R(x) 不足 m − 1 m-1 m1 次,多余的项系数补 0 0 0

输入输出样例
输入
5 1
1 9 2 6 0 8
1 7
输出
237340659 335104102 649004347 448191342 855638018
760903695
说明/提示
对于所有数据, 1 ≤ m < n ≤ 1 0 5 1 \le m < n \le 10^5 1m<n105 ,给出的系数均属于 [ 0 , 998244353 ) ∩ Z [0, 998244353) \cap \mathbb{Z} [0,998244353)Z

code

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define mod 998244353
#define MAXN 300005
#define int long long
int l, pi, ni, len, inv;
int a[MAXN], b[MAXN], c[MAXN], r[MAXN], d[MAXN], rev[MAXN];
int A[MAXN], B[MAXN], Binv[MAXN];

int qkpow ( int x, int y ) {
	int ans = 1;
	while ( y ) {
		if ( y & 1 )
			ans = ans * x % mod;
		x = x * x % mod;
		y >>= 1;
	}
	return ans;
}

void NTT ( int *c, int f, int len ) {
	for ( int i = 0;i < len;i ++ )
		if ( i < rev[i] )
			swap ( c[i], c[rev[i]] );
	for ( int i = 1;i < len;i <<= 1 ) {
		int omega = qkpow ( f == 1 ? pi : ni, ( mod - 1 ) / ( i << 1 ) );
		for ( int j = 0;j < len;j += ( i << 1 ) ) {
			int w = 1;
			for ( int k = 0;k < i;k ++, w = w * omega % mod ) {
				int x = c[k + j], y = w * c[k + j + i] % mod;
				c[k + j] = ( x + y ) % mod;
				c[k + j + i] = ( x - y + mod ) % mod;
			}
		}
	}
	inv = qkpow ( len, mod - 2 ); 
	if ( f == -1 )
		for ( int i = 0;i < len;i ++ )
			c[i] = c[i] * inv % mod;
}

void polyinv ( int deg, int *a, int *b ) {
	if ( deg == 1 ) {
		b[0] = qkpow ( a[0], mod - 2 );
		return;
	}
	polyinv ( ( deg + 1 ) >> 1, a, b );
	len = 1;
	l = 0;
	while ( len < deg * 2 - 1 ) {
		len <<= 1;
		l ++;
	}
	inv = qkpow ( len, mod - 2 );
	for ( int i = 0;i < len;i ++ )
		rev[i] = ( rev[i >> 1] >> 1 ) | ( ( i & 1 ) << ( l - 1 ) );
	for ( int i = 0;i < deg;i ++ )
		c[i] = a[i];
	for ( int i = deg;i < len;i ++ )
		c[i] = 0;
	NTT ( c, 1, len );
	NTT ( b, 1, len );
	for ( int i = 0;i < len;i ++ )
		b[i] = ( 2 - b[i] * c[i] % mod + mod ) % mod * b[i] % mod; 
	NTT ( b, -1, len );
	for ( int i = deg;i < len;i ++ )
		b[i] = 0;
}

void polydiv ( int n, int m,int *d, int *a, int *b, int *r ) {
	for ( int i = 0;i < n;i ++ )//进行反转操作 
		A[i] = a[n - i - 1];
	for ( int i = 0;i < m;i ++ )
		B[i] = b[m - i - 1];
	polyinv ( n - m + 1, B, Binv );//根据推导的公式求出B在mod x^(n-m+1)意义下的逆
	len = 1;
	l = 0;
	while ( len < ( n << 1 ) - m ) {
		len <<= 1;
		l ++;
	}
	for ( int i = 0;i < len;i ++ )
		rev[i] = ( rev[i >> 1] >> 1 ) | ( ( i & 1 ) << ( l - 1 ) );
	NTT ( A, 1, len );
	NTT ( Binv, 1, len );
	for ( int i = 0;i < len;i ++ )//求反转的商D 
		d[i] = A[i] * Binv[i] % mod;
	NTT ( d, -1, len ); 
	for ( int i = 0, j = n - m;i < j;i ++, j -- )//把商反转成正常 
		swap ( d[i], d[j] );
	//接下来搞r 
	memset ( B, 0, sizeof ( B ) );
	memset ( Binv, 0, sizeof ( Binv ) );
	memset ( rev, 0, sizeof ( rev ) );
	len = 1;
	l = 0;
	while ( len < n ) {
		l ++;
		len <<= 1;
	}
	for ( int i = 0;i < len;i ++ )
		rev[i] = ( rev[i >> 1] >> 1 ) | ( ( i & 1 ) << ( l - 1 ) ); 
	for ( int i = 0;i < m;i ++ )
		B[i] = b[i];
	for ( int i = 0;i < n - m + 1;i ++ )
		Binv[i] = d[i];
	NTT ( B, 1, len );
	NTT ( Binv, 1, len );
	for ( int i = 0;i < len;i ++ )
		r[i] = B[i] * Binv[i] % mod;
	NTT ( r, -1, len );
	for ( int i = 0;i < m;i ++ )
		r[i] = ( a[i] - r[i] + mod ) % mod;
}

signed main() {
	pi = 3;
	ni = mod / pi + 1;
	int n, m;
	scanf ( "%lld %lld", &n, &m );
	n ++;
	m ++; 
	for ( int i = 0;i < n;i ++ )
		scanf ( "%lld", &a[i] );
	for ( int i = 0;i < m;i ++ )
		scanf ( "%lld", &b[i] );
	polydiv ( n, m, d, a, b, r );
	for ( int i = 0;i < n - m + 1;i ++ )
		printf ( "%lld ", d[i] );
	printf ( "\n" );
	for ( int i = 0;i < m - 1;i ++ )
		printf ( "%lld ", r[i] );
	return 0;
}

多项式对数ln

理论推导

对于一个给定的多项式 A ( x ) A(x) A(x),求
l n A ( x ) lnA(x) lnA(x)


直接运用求导和积分完成,需要保证 A ( x ) A(x) A(x)的常数项为1

对于 f ( x ) , x f(x),x f(x),x点的导数: f ′ ( x ) = f ( x + △ ) − f ( x ) △ f'(x)=\frac{f(x+△)-f(x)}{△} f(x)=f(x+)f(x)

l n A ( x ) = ∫ l n ( A ( x ) ) ′ = ∫ A ( x ) ′ A ( x ) lnA(x)=∫ln(A(x))'=∫\frac{A(x)'}{A(x)} lnA(x)=ln(A(x))=A(x)A(x)


反正对于小孩子而言,我是难以理解求导和积分的,可能这里会留个坑,等学到后面,再慢慢跟他们玩吧,如果有dalao能教教我(可私信),我一定感激死你
在这里插入图片描述

模板

void polyln ( int n, int *a, int *b ) {
	polyinv ( n, a, b );
	for ( int i = 1;i < n;i ++ )
		tmp[i - 1] = a[i] * i % mod;
	int len = 1, l = 0;
	while ( len < n * 2 - 1 ) {
		len <<= 1;
		l ++;
	}
	for ( int i = 0;i < len;i ++ )
		rev[i] = ( rev[i >> 1] >> 1 ) | ( ( i & 1 ) << ( l - 1 ) );
	NTT ( tmp, 1, len );
	NTT ( b, 1, len );
	for ( int i = 0;i < len;i ++ )
		b[i] = tmp[i] * b[i] % mod;
	NTT ( b, -1, len );
	for ( int i = n - 1;i; i -- )
		b[i] = b[i - 1] * qkpow ( i, mod - 2 ) % mod;
	b[0] = 0; 
	for ( int i = n;i < len;i ++ )
		b[i] = 0;
} 

例题

题目

luogu
给出 n − 1 n−1 n1 次多项式 A ( x ) A(x) A(x),求一个   m o d     x n \bmod{\:x^n} modxn下的多项式 B ( x ) B(x) B(x),满足 B ( x ) ≡ ln ⁡ A ( x ) B(x) \equiv \ln A(x) B(x)lnA(x).
mod  998244353 \text{mod } 998244353 mod 998244353 下进行,且 a i ∈ [ 0 , 998244353 ] ∩ Z a_i\in [0, 998244353] \cap \mathbb{Z} ai[0,998244353]Z

输入格式
第一行一个整数 n n n
下一行有 n n n 个整数,依次表示多项式的系数 a 0 , a 1 , ⋯   , a n − 1 a_0, a_1, \cdots, a_{n-1} a0,a1,,an1
输出格式
输出 n n n 个整数,表示答案多项式中的系数 a 0 , a 1 , ⋯   , a n − 1 a_0, a_1, \cdots, a_{n-1} a0,a1,,an1

输入输出样例
输入
6
1 927384623 878326372 3882 273455637 998233543
输出
0 927384623 817976920 427326948 149643566 610586717
说明/提示
对于 100 % 100\% 100% 的数据, n ≤ 1 0 5 n \le 10^5 n105

code

#include <cstdio>
#include <iostream>
using namespace std;
#define mod 998244353
#define MAXN 300005
#define int long long
int n, pi, ni, inv;
int a[MAXN], b[MAXN], c[MAXN], rev[MAXN], tmp[MAXN];

int qkpow ( int x, int y ) {
	int ans = 1;
	while ( y ) {
		if ( y & 1 )
			ans = ans * x % mod;
		x = x * x % mod;
		y >>= 1;
	}
	return ans;
}

void NTT ( int *c, int f, int len ) {
	for ( int i = 0;i < len;i ++ )
		if ( i < rev[i] )
			swap ( c[i], c[rev[i]] );
	for ( int i = 1;i < len;i <<= 1 ) {
		int omega = qkpow ( f == 1 ? pi : ni, ( mod - 1 ) / ( i << 1 ) );
		for ( int j = 0;j < len;j += ( i << 1 ) ) {
			int w = 1;
			for ( int k = 0;k < i;k ++, w = w * omega % mod ) {
				int x = c[k + j], y = w * c[k + j + i] % mod;
				c[k + j] = ( x + y ) % mod;
				c[k + j + i] = ( x - y + mod ) % mod;
			}
		}
	}
	inv = qkpow ( len, mod - 2 );
	if ( f == -1 )
		for ( int i = 0;i < len;i ++ )
			c[i] = c[i] * inv % mod;
}

void polyinv ( int deg, int *a, int *b ) {
	if ( deg == 1 ) {
		b[0] = qkpow ( a[0], mod - 2 );
		return;
	}
	polyinv ( ( deg + 1 ) >> 1, a, b );
	int len = 1, l = 0;
	while ( len < deg * 2 - 1 ) {
		len <<= 1;
		l ++;
	}
	for ( int i = 0;i < len;i ++ )
		rev[i] = ( rev[i >> 1] >> 1 ) | ( ( i & 1 ) << ( l - 1 ) );
	for ( int i = 0;i < deg;i ++ )
		c[i] = a[i];
	for ( int i = deg;i < len;i ++ )
		c[i] = 0;
	NTT ( c, 1, len );
	NTT ( b, 1, len );
	for ( int i = 0;i < len;i ++ )
		b[i] = ( 2 - b[i] * c[i] % mod + mod ) % mod * b[i] % mod; 
	NTT ( b, -1, len );
	for ( int i = deg;i < len;i ++ )
		b[i] = 0;
}

void polyln ( int n, int *a, int *b ) {
	polyinv ( n, a, b );
	for ( int i = 1;i < n;i ++ )
		tmp[i - 1] = a[i] * i % mod;
	int len = 1, l = 0;
	while ( len < n * 2 - 1 ) {
		len <<= 1;
		l ++;
	}
	for ( int i = 0;i < len;i ++ )
		rev[i] = ( rev[i >> 1] >> 1 ) | ( ( i & 1 ) << ( l - 1 ) );
	NTT ( tmp, 1, len );
	NTT ( b, 1, len );
	for ( int i = 0;i < len;i ++ )
		b[i] = tmp[i] * b[i] % mod;
	NTT ( b, -1, len );
	for ( int i = n - 1;i; i -- )
		b[i] = b[i - 1] * qkpow ( i, mod - 2 ) % mod;
	b[0] = 0; 
	for ( int i = n;i < len;i ++ )
		b[i] = 0;
} 

signed main() {
	pi = 3;
	ni = mod / pi + 1;
	scanf ( "%lld", &n );
	for ( int i = 0;i < n;i ++ )
		scanf ( "%lld", &a[i] );
	polyln ( n, a, b );
	for ( int i = 0;i < n;i ++ )
		printf ( "%lld ", b[i] );
	return 0;
}

多项式开根sqrt

理论推导

对于一个给定的多项式 A ( x ) A(x) A(x),求 B ( x ) B(x) B(x)满足
B 2 ( x ) ≡ A ( x ) ( m o d   x n ) B^2(x)≡A(x) (mod\ x^n) B2(x)A(x)(mod xn)


如果 n = 1 n=1 n=1就直接是常数项开方
1. 1. 1.可以直接套牛顿迭代
B ( x ) = B 0 − B 0 2 ( x ) − A ( x ) 2 B 0 ( x ) = B 0 ( x ) + A ( x ) B 0 ( x ) 2 B(x)=B_0-\frac{B_0^2(x)-A(x)}{2B_0(x)}=\frac{B_0(x)+\frac{A(x)}{B_0(x)}}{2} B(x)=B02B0(x)B02(x)A(x)=2B0(x)+B0(x)A(x)


在这里插入图片描述
2. 2. 2.也可以用逆元平方的思想
B ′ ( x ) B'(x) B(x)满足 B ′ 2 ( x ) ≡ A ( x ) ( m o d   x ⌈ n 2 ⌉ ) B'^2(x)\equiv A(x)(mod\ x^{\lceil \frac{n}{2}\rceil}) B2(x)A(x)(mod x2n),且我们已经知道它了
两式相减 B 2 ( x ) − B ′ 2 ( x ) ≡ 0 ( m o d   x ⌈ n 2 ⌉ ) B^2(x)-B'^2(x)\equiv 0(mod\ x^{\lceil \frac{n}{2}\rceil}) B2(x)B2(x)0(mod x2n)
因式分解 ( B ( x ) − B ′ ( x ) ) ( B ( x ) + B ′ ( x ) ≡ 0 ( m o d   x ⌈ n 2 ⌉ ) (B(x)-B'(x))(B(x)+B'(x)\equiv 0(mod\ x^{\lceil \frac{n}{2}\rceil}) (B(x)B(x))(B(x)+B(x)0(mod x2n)
肯定只能是 B ( x ) − B ′ ( x ) ≡ 0 ( m o d   x ⌈ n 2 ⌉ ) B(x)-B'(x)\equiv 0(mod\ x^{\lceil \frac{n}{2}\rceil}) B(x)B(x)0(mod x2n)不可能非负数系数开个根还能开出负数来,那你可以被飞签高等学府了
故伎重演,用求逆元的方式平方来一下,这里就直接打开了 B 2 ( x ) + B ′ 2 ( x ) − 2 ∗ B ( x ) ∗ B ′ ( x ) ≡ 0 ( m o d   x n ) B^2(x)+B'^2(x)-2*B(x)*B'(x)\equiv 0(mod\ x^n) B2(x)+B2(x)2B(x)B(x)0(mod xn)
结合 B 2 ( x ) B^2(x) B2(x)需要满足的条件,替换成 A ( x ) A(x) A(x)
A ( x ) + B ′ 2 ( x ) − 2 ∗ B ( x ) ∗ B ′ ( x ) ≡ 0 ( m o d   x n ) A(x)+B'^2(x)-2*B(x)*B'(x)\equiv 0(mod\ x^n) A(x)+B2(x)2B(x)B(x)0(mod xn)
B ′ ( x ) , A ( x ) B'(x),A(x) B(x),A(x)去表示 B ( x ) B(x) B(x)
B ( x ) ≡ A ( x ) + B ′ ( x ) 2 2 ∗ B ′ ( x ) B(x)\equiv \frac{A(x)+B'(x)^2}{2*B'(x)} B(x)2B(x)A(x)+B(x)2


最后发现是孪生兄弟, 御用递归求解 O ( n l o g n ) O(nlogn) O(nlogn)

同时发现,只要常数项是二次项剩余且有逆元,这个多项式就可以开方
——摘自某dalao

模板

代码中常数项刚好是 1 1 1,不是加强版,我太弱了
B ( x ) ≡ A ( x ) B ′ ( x ) + B ′ ( x ) 2 B(x)\equiv \frac{\frac{A(x)}{B'(x)}+B'(x)}{2} B(x)2B(x)A(x)+B(x)

void polysqrt ( int n, int *a, int *b ) {
	if ( n == 1 ) {
		b[0] = 1;
		return;
	}
	polysqrt ( ( n + 1 ) >> 1, a, b );
	static int tmp[MAXN], binv[MAXN];
	polyinv ( n, b, binv );//b就是公式的B'(x) tmp就是公式的A(x)
	int len = 1, l = 0;
	while ( len < n * 2 - 1 ) {
		len <<= 1;
		l ++;
	}
	for ( int i = 0;i < len;i ++ )
		rev[i] = ( rev[i >> 1] >> 1 ) | ( ( i & 1 ) << ( l - 1 ) );
	for ( int i = 0;i < n;i ++ )
		tmp[i] = a[i];
	for ( int i = n;i < len;i ++ )
		tmp[i] = 0;
	NTT ( tmp, 1, len );
	NTT ( b, 1, len );
	NTT ( binv, 1, len );
	for ( int i = 0;i < len;i ++ )//求解B(x) 用b表示
		b[i] = ( tmp[i] * binv[i] % mod + b[i] ) % mod * inv2 % mod;
	NTT ( b, -1, len );
	for ( int i = n;i < len;i ++ )
		b[i] = binv[i] = 0;
	for ( int i = 0;i < n;i ++ )
		binv[i] = 0;
}

例题

题目

luogu
给定一个 n − 1 n-1 n1次多项式 A ( x ) A(x) A(x),求一个在   m o d     x n \bmod\ x^n mod xn
意义下的多项式 B ( x ) B(x) B(x),使得 B 2 ( x ) ≡ A ( x )   (   m o d     x n ) B^2(x) \equiv A(x) \ (\bmod\ x^n) B2(x)A(x) (mod xn)若有多解,请取零次项系数较小的作为答案。

多项式的系数在   m o d     998244353 \bmod\ 998244353 mod 998244353的意义下进行运算。

输入格式
第一行一个正整数 n n n
接下来 n n n个整数,依次表示多项式的系数 a 0 , a 1 , … , a n − 1 a_0, a_1, \dots, a_{n-1} a0,a1,,an1
输出格式
输出 n n n个整数,表示答案多项式的系数 b 0 , b 1 , … , b n − 1 b_0, b_1, \dots, b_{n-1} b0,b1,,bn1

输入输出样例
输入
3
1 2 1
输出
1 1 0

输入
7
1 8596489 489489 4894 1564 489 35789489
输出
1 503420421 924499237 13354513 217017417 707895465 411020414
说明/提示
对于 100 % 100\% 100%的数据: n ≤ 1 0 5 a i ∈ [ 0 , 998244352 ] ∩ Z n \leq 10^5 \qquad a_i \in [0,998244352] \cap \mathbb{Z} n105ai[0,998244352]Z

code

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define mod 998244353
#define MAXN 300005
#define int long long
int pi, ni, inv, inv2;
int rev[MAXN];

int qkpow ( int x, int y ) {
	int ans = 1;
	while ( y ) {
		if ( y & 1 )
			ans = ans * x % mod;
		x = x * x % mod;
		y >>= 1;
	}
	return ans;
}

void NTT ( int *c, int f, int len ) {
	for ( int i = 0;i < len;i ++ )
		if ( i < rev[i] )
			swap ( c[i], c[rev[i]] );
	for ( int i = 1;i < len;i <<= 1 ) {
		int omega = qkpow ( f == 1 ? pi : ni, ( mod - 1 ) / ( i << 1 ) );
		for ( int j = 0;j < len;j += ( i << 1 ) ) {
			int w = 1;
			for ( int k = 0;k < i;k ++, w = w * omega % mod ) {
				int x = c[k + j], y = w * c[k + j + i] % mod;
				c[k + j] = ( x + y ) % mod;
				c[k + j + i] = ( x - y + mod ) % mod;
			}
		}
	}
	inv = qkpow ( len, mod - 2 );
	if ( f == -1 )
		for ( int i = 0;i < len;i ++ )
			c[i] = c[i] * inv % mod;
}

void polyinv ( int deg, int *a, int *b ) {
	static int c[MAXN];
	if ( deg == 1 ) {
		b[0] = qkpow ( a[0], mod - 2 );
		return;
	}
	polyinv ( ( deg + 1 ) >> 1, a, b );
	int len = 1, l = 0;
	while ( len < deg * 2 - 1 ) {
		len <<= 1;
		l ++;
	}
	for ( int i = 0;i < len;i ++ )
		rev[i] = ( rev[i >> 1] >> 1 ) | ( ( i & 1 ) << ( l - 1 ) );
	for ( int i = 0;i < deg;i ++ )
		c[i] = a[i];
	for ( int i = deg;i < len;i ++ )
		c[i] = 0;
	NTT ( c, 1, len );
	NTT ( b, 1, len );
	for ( int i = 0;i < len;i ++ )
		b[i] = ( 2 - b[i] * c[i] % mod + mod ) % mod * b[i] % mod; 
	NTT ( b, -1, len );
	for ( int i = deg;i < len;i ++ )
		b[i] = 0;
}

void polysqrt ( int n, int *a, int *b ) {
	if ( n == 1 ) {
		b[0] = 1;
		return;
	}
	polysqrt ( ( n + 1 ) >> 1, a, b );
	static int tmp[MAXN], binv[MAXN];
	polyinv ( n, b, binv );
	int len = 1, l = 0;
	while ( len < n * 2 - 1 ) {
		len <<= 1;
		l ++;
	}
	for ( int i = 0;i < len;i ++ )
		rev[i] = ( rev[i >> 1] >> 1 ) | ( ( i & 1 ) << ( l - 1 ) );
	for ( int i = 0;i < n;i ++ )
		tmp[i] = a[i];
	for ( int i = n;i < len;i ++ )
		tmp[i] = 0;
	NTT ( tmp, 1, len );
	NTT ( b, 1, len );
	NTT ( binv, 1, len );
	for ( int i = 0;i < len;i ++ )
		b[i] = ( tmp[i] * binv[i] % mod + b[i] ) % mod * inv2 % mod;
	NTT ( b, -1, len );
	for ( int i = n;i < len;i ++ )
		b[i] = binv[i] = 0;
	for ( int i = 0;i < n;i ++ )
		binv[i] = 0;
}

int a[MAXN], b[MAXN];

signed main() {
	pi = 3;
	ni = mod / pi + 1;
	inv2 = qkpow ( 2, mod - 2 );
	int n;
	scanf ( "%lld", &n );
	for ( int i = 0;i < n;i ++ )
		scanf ( "%lld", &a[i] );
	polysqrt ( n, a, b );
	for ( int i = 0;i < n;i ++ )
		printf ( "%lld ", b[i] );
	return 0;
}

多项式指数exp

理论推导

对于一个给定的多项式 A ( x ) A(x) A(x),求 B ( x ) B(x) B(x)满足 B ( x ) ≡ e A ( x ) ( m o d   x n ) B(x) \equiv \text e^{A(x)}(mod\ x^n) B(x)eA(x)(mod xn)


取个对数,移个项 l n ( B ( x ) ) ≡ A ( x ) ( m o d   x n ) — — > l n ( B ( x ) ) − A ( x ) ≡ 0 ( m o d   x n ) ln(B(x))\equiv A(x)(mod\ x^n)——>ln(B(x))-A(x)\equiv 0(mod\ x^n) ln(B(x))A(x)(mod xn)>ln(B(x))A(x)0(mod xn)


对数求导公式: ( l n   x ) ′ = 1 x (ln\ x)'=\frac{1}{x} (ln x)=x1别问我为什么,我也证不来,求教

接着套牛顿迭代 B ( x ) ≡ B 0 ( x ) − g ( B 0 ( x ) ) g ′ ( B 0 ( x ) ) ≡ B 0 ( x ) − g ( B 0 ( x ) ) 1 B 0 ≡ B 0 ( x ) − l n B 0 ( x ) − A ( x ) 1 B 0 ( x ) B(x)\equiv B_0(x)-\frac{g(B_0(x))}{g'(B_0(x))}\equiv B_0(x)-\frac{g(B_0(x))}{\frac{1}{B_0}}\equiv B_0(x)-\frac{lnB_0(x)-A(x)}{\frac{1}{B_0(x)}} B(x)B0(x)g(B0(x))g(B0(x))B0(x)B01g(B0(x))B0(x)B0(x)1lnB0(x)A(x) ≡ B 0 ( x ) − B 0 ( x ) ( l n B 0 ( x ) − A ( x ) ) ≡ B 0 ( x ) ( 1 − l n B 0 ( x ) + A ( x ) )   ( m o d   x 2 n ) \equiv B_0(x)-B_0(x)(lnB_0(x)-A(x))\equiv B_0(x)(1-lnB_0(x)+A(x))\ (mod\ x^{2n}) B0(x)B0(x)(lnB0(x)A(x))B0(x)(1lnB0(x)+A(x)) (mod x2n)
在这里插入图片描述
必须保证A(x)常数项为0,B(x)常数项为1不知道为什么,求dalao教

模板

void polyexp ( int n, int *a, int *b ) {
	if ( n == 1 ) {
		b[0] = 1;
		return;
	}
	polyexp ( ( n + 1 ) >> 1, a, b );
	static int bln[MAXN];
	polyln ( n, b, bln );
	for ( int i = 0;i < n;i ++ )
		bln[i] = ( i == 0 ) + a[i] - bln[i] + mod;
	int len = 1, l = 0;
	while ( len < n * 2 - 1 ) {
		len <<= 1;
		l ++;
	}
	for ( int i = 0;i < len;i ++ )
		rev[i] = ( rev[i >> 1] >> 1 ) | ( ( i & 1 ) << ( l - 1 ) );
	NTT ( bln, 1, len );
	NTT ( b, 1, len );
	for ( int i = 0;i < len;i ++ )
		b[i] = b[i] * bln[i] % mod;
	NTT ( b, -1, len );
	for ( int i = n;i < len;i ++ )
		b[i] = bln[i] = 0;
	for ( int i = 0;i < n;i ++ )
		bln[i] = 0;
}

例题

luogu
给出 n − 1 n-1 n1 次多项式 A ( x ) A(x) A(x),求一个   m o d     x n \bmod{\:x^n} modxn下的多项式 B ( x ) B(x) B(x),满足 B ( x ) ≡ e A ( x ) B(x) \equiv \text e^{A(x)} B(x)eA(x),系数对 998244353 998244353 998244353 取模
输入格式
第一行一个整数 n n n

下一行有 n n n 个整数,依次表示多项式的系数 a 0 , a 1 , ⋯   , a n − 1 a_0, a_1, \cdots, a_{n-1} a0,a1,,an1
输出格式
输出 n n n 个整数,表示答案多项式中的系数 a 0 , a 1 , ⋯   , a n − 1 a_0, a_1, \cdots, a_{n-1} a0,a1,,an1

输入输出样例
输入
6
0 927384623 817976920 427326948 149643566 610586717
输出
1 927384623 878326372 3882 273455637 998233543
说明/提示
对于 100 % 100\% 100% 的数据, n ≤ 1 0 5 n \le 10^5 n105

题目

code

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define mod 998244353
#define MAXN 300005
#define int long long
int pi, ni, inv;
int rev[MAXN];

int qkpow ( int x, int y ) {
	int ans = 1;
	while ( y ) {
		if ( y & 1 )
			ans = ans * x % mod;
		x = x * x % mod;
		y >>= 1;
	}
	return ans;
}

void NTT ( int *c, int f, int len ) {
	for ( int i = 0;i < len;i ++ )
		if ( i < rev[i] )
			swap ( c[i], c[rev[i]] );
	for ( int i = 1;i < len;i <<= 1 ) {
		int omega = qkpow ( f == 1 ? pi : ni, ( mod - 1 ) / ( i << 1 ) );
		for ( int j = 0;j < len;j += ( i << 1 ) ) {
			int w = 1;
			for ( int k = 0;k < i;k ++, w = w * omega % mod ) {
				int x = c[k + j], y = w * c[k + j + i] % mod;
				c[k + j] = ( x + y ) % mod;
				c[k + j + i] = ( x - y + mod ) % mod;
			}
		}
	}
	inv = qkpow ( len, mod - 2 );
	if ( f == -1 )
		for ( int i = 0;i < len;i ++ )
			c[i] = c[i] * inv % mod;
}

void polyinv ( int deg, int *a, int *b ) {
	static int c[MAXN];
	if ( deg == 1 ) {
		b[0] = qkpow ( a[0], mod - 2 );
		return;
	}
	polyinv ( ( deg + 1 ) >> 1, a, b );
	int len = 1, l = 0;
	while ( len < deg * 2 - 1 ) {
		len <<= 1;
		l ++;
	}
	for ( int i = 0;i < len;i ++ )
		rev[i] = ( rev[i >> 1] >> 1 ) | ( ( i & 1 ) << ( l - 1 ) );
	for ( int i = 0;i < deg;i ++ )
		c[i] = a[i];
	for ( int i = deg;i < len;i ++ )
		c[i] = 0;
	NTT ( c, 1, len );
	NTT ( b, 1, len );
	for ( int i = 0;i < len;i ++ )
		b[i] = ( 2 - b[i] * c[i] % mod + mod ) % mod * b[i] % mod; 
	NTT ( b, -1, len );
	for ( int i = deg;i < len;i ++ )
		b[i] = 0;
}

void polyln ( int n, int *a, int *b ) {
	static int tmp[MAXN];
	polyinv ( n, a, b );
	for ( int i = 1;i < n;i ++ )
		tmp[i - 1] = a[i] * i % mod;
	int len = 1, l = 0;
	while ( len < n * 2 - 1 ) {
		len <<= 1;
		l ++;
	}
	for ( int i = 0;i < len;i ++ )
		rev[i] = ( rev[i >> 1] >> 1 ) | ( ( i & 1 ) << ( l - 1 ) );
	NTT ( tmp, 1, len );
	NTT ( b, 1, len );
	for ( int i = 0;i < len;i ++ )
		b[i] = tmp[i] * b[i] % mod;
	NTT ( b, -1, len );
	for ( int i = n - 1;i; i -- )
		b[i] = b[i - 1] * qkpow ( i, mod - 2 ) % mod;
	b[0] = 0;
	for ( int i = n;i < len;i ++ )
		b[i] = 0;
} 

void polyexp ( int n, int *a, int *b ) {
	if ( n == 1 ) {
		b[0] = 1;
		return;
	}
	polyexp ( ( n + 1 ) >> 1, a, b );
	static int bln[MAXN];
	polyln ( n, b, bln );
	for ( int i = 0;i < n;i ++ )
		bln[i] = ( i == 0 ) + a[i] - bln[i] + mod;
	int len = 1, l = 0;
	while ( len < n * 2 - 1 ) {
		len <<= 1;
		l ++;
	}
	for ( int i = 0;i < len;i ++ )
		rev[i] = ( rev[i >> 1] >> 1 ) | ( ( i & 1 ) << ( l - 1 ) );
	NTT ( bln, 1, len );
	NTT ( b, 1, len );
	for ( int i = 0;i < len;i ++ )
		b[i] = b[i] * bln[i] % mod;
	NTT ( b, -1, len );
	for ( int i = n;i < len;i ++ )
		b[i] = bln[i] = 0;
	for ( int i = 0;i < n;i ++ )
		bln[i] = 0;
}

int a[MAXN], b[MAXN];

signed main() {
	pi = 3;
	ni = mod / pi + 1;
	int n;
	scanf ( "%lld", &n );
	for ( int i = 0;i < n;i ++ )
		scanf ( "%lld", &a[i] );
	polyexp ( n, a, b );
	for ( int i = 0;i < n;i ++ )
		printf ( "%lld ", b[i] );
	return 0;
}

多项式快速幂

理论推导

通过上述多项式的基本操作后,我们直接暴力来一发拆公式
在这里插入图片描述
B ( x ) ≡ A k ( x )   ( m o d x n ) B(x)≡A^k(x)\ (mod x^n) B(x)Ak(x) (modxn)
= = > ==> ==> B ( x ) ≡ e l n A ( x ) k B(x)\equiv e^{lnA(x)^k} B(x)elnA(x)k
= = > ==> ==> B ( x ) ≡ e k ∗ l n A ( x ) B(x)\equiv e^{k*lnA(x)} B(x)eklnA(x)
所以我们只用上述所讲的 l n , e x p ln,exp ln,exp即可完成快速幂

例题

题目

luogu
给定一个 n − 1 n−1 n1次多项式 A ( x ) A(x) A(x),求一个在   m o d     x n \bmod\ x^n mod xn
意义下的多项式 B ( x ) B(x) B(x),使得 B ( x ) ≡ A k ( x )   (   m o d     x n ) B(x) \equiv A^k(x) \ (\bmod\ x^n) B(x)Ak(x) (mod xn)
多项式的系数在   m o d     998244353 \bmod\ 998244353 mod 998244353的意义下进行运算。

输入格式
第一行两个正整数 n , k n,k n,k

接下来 n n n个整数,依次表示多项式的系数 a 0 , a 1 , … , a n − 1 a_0, a_1, \dots, a_{n-1} a0,a1,,an1
输出格式
输出 n n n个整数,表示答案多项式的系数 b 0 , b 1 , … , b n − 1 b_0, b_1, \dots, b_{n-1} b0,b1,,bn1

输入输出样例
输入
9 18948465
1 2 3 4 5 6 7 8 9
输出
1 37896930 597086012 720637306 161940419 360472177 560327751 446560856 524295016

输入
4 1
1 1 0 0
输出
1 1 0 0

输入
4 2
1 1 0 0
输出
1 2 1 0

输入
4 3
1 1 0 0
输出
1 3 3 1
说明/提示
对于 100 % 100\% 100%的数据: n ≤ 1 0 5 2 ≤ k ≤ 1 0 1 0 5 a i ∈ [ 0 , 998244352 ] ∩ Z n \leq 10^5 \qquad 2 \leq k \leq 10^{10^5}\qquad a_i \in [0,998244352] \cap \mathbb{Z} n1052k10105ai[0,998244352]Z

code

唯一的坑点🕳就是注意 k k k很大, l o n g   l o n g long\ long long long装不下,必须快读取模

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define mod 998244353
#define MAXN 300005
#define int long long
int pi, ni, inv;
int rev[MAXN], result[MAXN];

int qkpow ( int x, int y ) {
	int ans = 1;
	while ( y ) {
		if ( y & 1 )
			ans = ans * x % mod;
		x = x * x % mod;
		y >>= 1;
	}
	return ans;
}

void NTT ( int *c, int f, int len ) {
	for ( int i = 0;i < len;i ++ )
		if ( i < rev[i] )
			swap ( c[i], c[rev[i]] );
	for ( int i = 1;i < len;i <<= 1 ) {
		int omega = qkpow ( f == 1 ? pi : ni, ( mod - 1 ) / ( i << 1 ) );
		for ( int j = 0;j < len;j += ( i << 1 ) ) {
			int w = 1;
			for ( int k = 0;k < i;k ++, w = w * omega % mod ) {
				int x = c[k + j], y = w * c[k + j + i] % mod;
				c[k + j] = ( x + y ) % mod;
				c[k + j + i] = ( x - y + mod ) % mod;
			}
		}
	}
	inv = qkpow ( len, mod - 2 );
	if ( f == -1 )
		for ( int i = 0;i < len;i ++ )
			c[i] = c[i] * inv % mod;
}

void polyinv ( int deg, int *a, int *b ) {
	static int c[MAXN];
	if ( deg == 1 ) {
		b[0] = qkpow ( a[0], mod - 2 );
		return;
	}
	polyinv ( ( deg + 1 ) >> 1, a, b );
	int len = 1, l = 0;
	while ( len < deg * 2 - 1 ) {
		len <<= 1;
		l ++;
	}
	for ( int i = 0;i < len;i ++ )
		rev[i] = ( rev[i >> 1] >> 1 ) | ( ( i & 1 ) << ( l - 1 ) );
	for ( int i = 0;i < deg;i ++ )
		c[i] = a[i];
	for ( int i = deg;i < len;i ++ )
		c[i] = 0;
	NTT ( c, 1, len );
	NTT ( b, 1, len );
	for ( int i = 0;i < len;i ++ )
		b[i] = ( 2 - b[i] * c[i] % mod + mod ) % mod * b[i] % mod; 
	NTT ( b, -1, len );
	for ( int i = deg;i < len;i ++ )
		b[i] = 0;
}

void polyln ( int n, int *a, int *b ) {
	static int tmp[MAXN];
	polyinv ( n, a, b );
	for ( int i = 1;i < n;i ++ )
		tmp[i - 1] = a[i] * i % mod;
	int len = 1, l = 0;
	while ( len < n * 2 - 1 ) {
		len <<= 1;
		l ++;
	}
	for ( int i = 0;i < len;i ++ )
		rev[i] = ( rev[i >> 1] >> 1 ) | ( ( i & 1 ) << ( l - 1 ) );
	NTT ( tmp, 1, len );
	NTT ( b, 1, len );
	for ( int i = 0;i < len;i ++ )
		b[i] = tmp[i] * b[i] % mod;
	NTT ( b, -1, len );
	for ( int i = n - 1;i; i -- )
		b[i] = b[i - 1] * qkpow ( i, mod - 2 ) % mod;
	b[0] = 0;
	for ( int i = n;i < len;i ++ )
		b[i] = tmp[i] = 0;
	for ( int i = 0;i < n;i ++ )
		tmp[i] = 0;
} 

void polyexp ( int n, int *a, int *b ) {
	if ( n == 1 ) {
		b[0] = 1;
		return;
	}
	polyexp ( ( n + 1 ) >> 1, a, b );
	static int bln[MAXN];
	polyln ( n, b, bln );
	for ( int i = 0;i < n;i ++ )
		bln[i] = ( ( i == 0 ) + a[i] - bln[i] + mod ) % mod;
	int len = 1, l = 0;
	while ( len < n * 2 - 1 ) {
		len <<= 1;
		l ++;
	}
	for ( int i = 0;i < len;i ++ )
		rev[i] = ( rev[i >> 1] >> 1 ) | ( ( i & 1 ) << ( l - 1 ) );
	NTT ( bln, 1, len );
	NTT ( b, 1, len );
	for ( int i = 0;i < len;i ++ )
		b[i] = b[i] * bln[i] % mod;
	NTT ( b, -1, len );
	for ( int i = n;i < len;i ++ )
		b[i] = bln[i] = 0;
	for ( int i = 0;i < n;i ++ )
		bln[i] = 0;
}

int a[MAXN], b[MAXN];

void read ( int &x ) {
	x = 0;char s = getchar();
	while ( s < '0' || s > '9' )
		s = getchar();
	while ( s >= '0' && s <= '9' ) {
		x = ( ( x << 1 ) + ( x << 3 ) + ( s - '0' ) ) % mod;
		s = getchar();
	}
} 

signed main() {
	pi = 3;
	ni = mod / pi + 1;
	int n, k;
	scanf ( "%lld", &n );
	read ( k );//k太大了,必须要取模long long装不下 
	for ( int i = 0;i < n;i ++ )
		scanf ( "%lld", &a[i] );
	polyln ( n, a, b );
	for ( int i = 0;i < n;i ++ )
		b[i] = b[i] * k % mod;
	polyexp ( n, b, result );
	for ( int i = 0;i < n;i ++ )
		printf ( "%lld ", result[i] );
	return 0;
}

版权申明:
教会我多项式的blog
证明无代码

最后只想说一句,希望各个dalao能完善我的证明,甚至教教这个蒟蒻,部分实在证不来,百度百科长的又丑有问题欢迎评论,受教了
在这里插入图片描述

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值