多项式
多项式在计算机中一般以数组方式储存,假设有一个多项式f(x),并且x^i前面的系数为ai,那么显然我们恰好可以用一个数组a来描述这个多项式,
多项式的系数ai恰好存在数组a的第i项a[i];
多项式dft
在一般的多项式中,如果模数允许,乘法采取ntt来做,因为速度较快,多项式乘法是整个多项式体系的基石,他的常数如果太大,将影响到后面的所
有的多项式操作,
多项式前期预处理
我们需要设置多项式最大长度,模数,原根,多项式最大长度的log,以及关键点reduce函数,传入一个-mod到mod之间的数x,返回x%mod,他比取模更快
然后是快速幂,在往后是复数i在模意义下的值,2i在模意义下的逆元,以及后面的逆元数组和他的初始化函数
const int maxn=100005<<2,mod = 998244353,g=3;// const 能明显加速代码
const int lgmaxn=20;
#define reduce(x) ((x)+(((x)>>31)&mod))
int qpow(int a,int b){
int ret=1;
while(b){
if(b&1) ret=1ll*ret*a%mod;
a=1ll*a*a%mod;
b>>=1;
}
return ret;
}
int I=86583718;
int inv2=qpow(2,mod-2);
int inv2I=qpow(2*I,mod-2);
int inv[maxn]={1,1};
void inv_ini(){
for(int i=2;i<maxn;++i) inv[i]=1ll*(mod-mod/i)*(inv[mod%i])%mod;
}
dft
蝴蝶变化必须预处理出来,否则太慢,最前面的是蝴蝶变换r[i][j]数组, 之后是ntt单位根wn数组和他的逆元数组。接着是ntt的数组的预处理函数以及ntt函数本体
int allr[maxn<<1],*r[30],ntt_wn[30],ntt_revwn[30]; // 特别详细,没啥其他可说的
//https://wenku.baidu.com/view/b438a51cce84b9d528ea81c758f5f61fb7362826.html
void dft_ini(){ // ntt 高性能
r[0]=allr;
for(int n=1,bit=0;n<maxn;n<<=1,bit++){
for(int i=0;i<n;++i)r[bit][i]=(r[bit][i>>1]>>1)|((i&1)<<(bit-1));
r[bit+1]=r[bit]+n;
ntt_wn[bit]=qpow(g,(mod-1)>>bit);// 单位根
ntt_revwn[bit]=qpow(ntt_wn[bit],mod-2);//逆变换则乘上逆元
}
}
void dft(int*a,int n,int bits,int opt){ // ntt
for(int i=0;i<n;++i) if(i<r[bits][i]) swap(a[i],a[r[bits][i]]);
for(int k=1,bit=0;k<n;k<<=1,bit++){
int wn=opt==1?ntt_wn[bit+1]:ntt_revwn[bit+1];
for (int i=0;i<n;i+=(k<<1)){
for (int j=0,w=1; j<k; j++,w=1ll*w*wn%mod){
int x=a[i+j], y=1ll*w*a[i+j+k]%mod;
a[i+j]=reduce(x+y-mod), a[i+j+k]=reduce(x-y);
}
}
}
if(opt==-1) for(int i=0;i<n;++i) a[i]=1ll*a[i]*inv[n]%mod;
}//1800ms+
多项式拓展与对齐
为了后期代码可观性良好,以及少写一些奇怪的代码,poly_cpy(int*a,int n,int*b,int exn)提供了多项式拷贝等操作,即将一个多项式拷贝到另外一个
多项式,必须保证exn>=n ,即先将a[0...n-1]拷贝到b[0...n-1] 然后把b数组多余的部分清零。这里如果ab为同一个数组,就不必进行拷贝了。
void poly_cpy(int*a,int n,int *b,int exn){//
if(a!=b) memcpy(b,a,sizeof(b[0])*n);
if(n<exn) memset(b+n,0,sizeof(b[0])*(exn-n));
}
多项式乘法
为了防止常数问题,这里依旧采取最简单的方式,不让数组发生过多的拷贝,我们只实现a*=b这一个步骤,不实现c=a*b这种,很套路的乘法,先变为点指表示法
然后变为系数表示法即可
void poly_mul(int*a,int na,int*b,int nb){
static int c[maxn];
int exn=1,bits=0;
while(exn<na+nb-1) exn<<=1,bits++;
poly_cpy(a,na,a,exn);
poly_cpy(b,nb,c,exn);
dft(a,exn,bits,1);
dft(c,exn,bits,1);
for(int i=0;i<exn;i++) a[i]=1ll*a[i]*c[i]%mod;
dft(a,exn,bits,-1);
}
多项式求逆
若两个多项式f和g,求出f*g然后让系数对mod取模,多项式忽略高于x^k次的项后等于1,则f和g互逆
这个地方很难以理解,因为有两个取模,所以会让很多初学者感到困惑,只要注意两个模是不同的,一个是系数对mod取模,另一个是多项式整体对x^k取模,即可
整个算法采取牛顿迭代法来完成,很容易被数学归纳法证明,f(x)*g(x)===1(x^k,mod),当k等于1的时候,非常好得出结果,显然那时候g(x)至少需要
1项,即常数项,大于常数项的部分无效的,这个很容易证明,这一项等于f(x)的常数项的逆元。然后我们来根据f(x)*g(x)===1(x^k,mod)推出f(x)*g(x)===1(x^2k,mod)
的结果,以下是推导过程,显然g(x)=g(x)*(2-f(x)*g(x)),只要自己注意一下项数的变化即可,然后我们倍增即可得出答案时间复杂度T(n)=T(n/2)+nlg(n)
根据主定理T(n)=O(nlgn)
void poly_inv(int*a,int n,int*b){ // // %(x^n) b(2-ab)
static int c[maxn];
if(n==1) {b[0]=qpow(a[0],mod-2); return;}
poly_inv(a,(n+1)>>1,b);
int exn=1,bits=0;
while(exn<n+n-1) exn<<=1,bits++;
poly_cpy(b,(n+1)>>1,b,exn);
poly_cpy(a,n,c,exn);
dft(b,exn,bits,1);
dft(c,exn,bits,1);
for(int i=0;i<exn;i++) b[i]=b[i]*(2-1ll*c[i]*b[i]%mod+mod)%mod;
dft(b,exn,bits,-1);
}//p4738 开O2 500ms+ 洛谷最快200ms+
多项式除法
除法会有剩余,所以有两个返回值。
对于一个n-1次多项式f(x) 定义F(x)=x^nf(1/x) 则有以下推导

f到F只不过是系数翻转 我们可以通过求逆迅速计算D(x) 继而的得到d(x)
余数直接ntt暴力即可
void poly_div(int*a,int na,int*b,int nb,int*c,int*d){
reverse(a,a+na); reverse(b,b+nb);
poly_inv(b,na-nb+1,c); //exn(2*na-2*nb+1)=exn(2*na-nb-nb+1)
poly_mul(c,na-nb+1,a,na); //exn(na-nb+1+na-1)=exn(2*na-nb)
reverse(a,a+na); reverse(b,b+nb);
reverse(c,c+na-nb+1);
poly_cpy(c,na-nb+1,d,na-nb+1);
poly_mul(d,na-nb+1,b,nb);
for(int i=0;i<na;i++) d[i]=reduce(a[i]-d[i]);
}// p4738 开O2 600ms+ 洛谷最快300ms+
多项式取对数

于是求导、求逆、乘、积分即可完成
void poly_der(int*a,int n,int*b){ // 微分求导
for(int i=1;i<n;i++) b[i-1]=1ll*a[i]*i%mod;
}
void poly_int(int*a,int n,int*b){ // 默认积分结果项常的数为0
for(int i=1;i<=n;i++) b[i]=1ll*a[i-1]*inv[i]%mod;
b[0]=0;
}
void poly_ln(int*a,int n,int*b){// a[exn(n+n-1)] //保证f(0)=1 , 默认积分结果项常的数为0 ,b[0]=ln(a[0])=1
static int d[maxn];
poly_der(a,n,d); // n-1
poly_inv(a,n,b); // n
poly_mul(d,n-1,b,n);
poly_int(d,n,b); // b[0]=ln(a[0])=ln(1)=0
}//p4725 开O2 700ms+ 洛谷最快300ms+
多项式求指数函数
大佬已将讲的很清楚了,用泰勒展开求的
void poly_exp(int*a,int n,int*b){//a[exn(n+n-1)] // 保证f(0)=0 b(1-ln(b)+f),
static int a_lnb[maxn];
if(n==1) {b[0]=1;return;}
poly_exp(a,(n+1)>>1,b);
poly_cpy(b,(n+1)>>1,b,n);
poly_ln(b,n,a_lnb);
for(int i=0;i<n;++i) a_lnb[i]=reduce(a[i]-a_lnb[i]);
a_lnb[0]=reduce(a_lnb[0]+1-mod);
poly_mul(b,n,a_lnb,n);
}//p4726 开O2 1600ms+ 洛谷最快400ms+
多项式开根
递归边界改为二次剩余即可
void poly_sqrt(int*a,int n,int*b){//a[exn(n+n-1)] //保证a[0]=1, (b+a/b)/2 比上面那个好一些
static int c[maxn];
if(n==1) {b[0]=1;return;}
poly_sqrt(a,(n+1)>>1,b);
poly_cpy(b,(n+1)>>1,b,n);
poly_inv(b,n,c);
poly_mul(c,n,a,n);
int inv2=qpow(2,mod-2);
for(int i=0;i<n;i++) b[i]=1ll*(b[i]+c[i])*inv2%mod;
}// P5205 开O2 3200+ms 洛谷最快600ms++
多项式快速幂
先取对数,然后乘,然后取exp
void poly_pow(int *a,int n,int k,int *b){//a[exn(n+n-1)] // a^k not quick pow but quicker
static int c[maxn],d[maxn];
int t=0;
while(t<n&&a[t]==0) t++;// a[t]*x^t
if(1ll*t*k>=n)return; //k*t 居然能够爆int
a+=t;// a/=x^t
int invat=qpow(a[0],mod-2);
int mul=qpow(a[0],k);
for(int i=0;i<n-t;i++)d[i]=1ll*a[i]*invat%mod;
for(int i=n-t;i<n;i++)d[i]=0;
poly_ln(d,n,c);
for(int i=1;i<n;i++)c[i]=1ll*c[i]*k%mod;
poly_exp(c,n,b);
for(int i=n-1;i>=1ll*t*k;i--) b[i]=1ll*b[i-1ll*k*t]*mul%mod;
for(int i=1ll*k*t-1;i>=0;i--) b[i]=0;
// b*=x^kt
}// 若k为大数,且保证a[0]=1,传入k%mod即可
多项式三角函数
void poly_sin(int*a,int n,int*b){//a[exn(n+n-1)]// 保证a[0]=0 -> c[0]=0 -> 可以exp
static int c[maxn],d[maxn];
for(int i=0;i<n;i++) c[i]=1ll*a[i]*I%mod;
poly_exp(c,n,b);
poly_inv(b,n,d);
for(int i=0;i<n;i++) b[i]=1ll*(b[i]-d[i]+mod)*inv2I%mod;
} // P5264 开O2 2111ms 洛谷最快691ms
void poly_cos(int*a,int n,int*b){//a[exn(n+n-1)]// 保证a[0]=0 -> c[0]=0 -> 可以exp
static int c[maxn],d[maxn];
for(int i=0;i<n;i++) c[i]=1ll*a[i]*I%mod;
poly_exp(c,n,b);
poly_inv(b,n,d);
for(int i=0;i<n;i++) b[i]=1ll*(b[i]+d[i])*inv[2]%mod;
}// P5264 开O2 2111ms 洛谷最快691ms
void poly_asin(int*a,int n,int*b){//a[exn(n+n-1)]// 保证a[0]=0 积分: f'/(sqrt(1-f*f))
static int c[maxn];
poly_cpy(a,n,b,n);// f
poly_mul(b,n,b,n);// f*f
b[0]=reduce(b[0]-1);// f*f-1
for(int i=0;i<n;i++)b[i]=mod-b[i]; //1-ff
poly_sqrt_Strong(b,n,c);// sqrt(1-ff)
poly_inv(c,n,b); // 1/sqrt(1-ff)
poly_der(a,n,c);// f'
poly_mul(c,n,b,n);//
poly_int(c,n,b);// f'/(sqrt(1-f*f))
}// P5265 开O2 1626ms 洛谷最快985ms
void poly_atan(int*a,int n,int*b){//a[exn(n+n-1)] // 保证a[0]=0 积分: f'/(sqrt(1-f*f))
static int c[maxn];
poly_cpy(a,n,b,n);// f
poly_mul(b,n,b,n);// f*f
b[0]=reduce(b[0]+1-mod);// f*f+1
poly_inv(b,n,c); // 1/(f*f+1)
poly_der(a,n,b);// f'
poly_mul(c,n,b,n);//
poly_int(c,n,b);// f'/(f*f+1)
}// P5265 开O2 1626ms 洛谷最快985ms
拉格朗日插值法
。。。。。。没啥可说的,存粹的套路
多项式多点求值
完整代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=100005<<2,mod = 998244353,g=3;// const 能明显加速代码
const int lgmaxn=20;
#define reduce(x) ((x)+(((x)>>31)&mod))
int qpow(int a,int b){
int ret=1;
while(b){
if(b&1) ret=1ll*ret*a%mod;
a=1ll*a*a%mod;
b>>=1;
}
return ret;
}
int I=86583718;
int inv2I=qpow(2*I,mod-2);
int inv[maxn]={1,1};
void inv_ini(){
for(int i=2;i<maxn;++i) inv[i]=1ll*(mod-mod/i)*(inv[mod%i])%mod;
}
int allr[maxn<<1],*r[30],ntt_wn[30],ntt_revwn[30]; // 特别详细,没啥其他可说的
//https://wenku.baidu.com/view/b438a51cce84b9d528ea81c758f5f61fb7362826.html
void dft_ini(){ // ntt 高性能
r[0]=allr;
for(int n=1,bit=0;n<maxn;n<<=1,bit++){
for(int i=0;i<n;++i)r[bit][i]=(r[bit][i>>1]>>1)|((i&1)<<(bit-1));
r[bit+1]=r[bit]+n;
ntt_wn[bit]=qpow(g,(mod-1)>>bit);// 单位根
ntt_revwn[bit]=qpow(ntt_wn[bit],mod-2);//逆变换则乘上逆元
}
}
void dft(int*a,int n,int bits,int opt){ // ntt
for(int i=0;i<n;++i) if(i<r[bits][i]) swap(a[i],a[r[bits][i]]);
for(int k=1,bit=0;k<n;k<<=1,bit++){
int wn=opt==1?ntt_wn[bit+1]:ntt_revwn[bit+1];
for (int i=0;i<n;i+=(k<<1)){
for (int j=0,w=1; j<k; j++,w=1ll*w*wn%mod){
int x=a[i+j], y=1ll*w*a[i+j+k]%mod;
a[i+j]=reduce(x+y-mod), a[i+j+k]=reduce(x-y);
}
}
}
if(opt==-1) for(int i=0;i<n;++i) a[i]=1ll*a[i]*inv[n]%mod;
}//1800ms+
void poly_show(int*a,int n){
for(int i=0;i<n;i++) printf(" %dx^%d +",a[i]>1000?a[i]-mod:a[i],i);
cout<<endl;
}
void poly_cpy(int*a,int n,int *b,int exn){//
if(a!=b) memcpy(b,a,sizeof(b[0])*n);
if(n<exn) memset(b+n,0,sizeof(b[0])*(exn-n));
}
/*
a会被作拓展,但是b不会,所以b的范围不会改变
a[exn(na+nb-1)]
a*=b a和b允许是同一个数组
*/
void poly_mul(int*a,int na,int*b,int nb){
static int c[maxn];
int exn=1,bits=0;
while(exn<na+nb-1) exn<<=1,bits++;
poly_cpy(a,na,a,exn);
poly_cpy(b,nb,c,exn);
dft(a,exn,bits,1);
dft(c,exn,bits,1);
for(int i=0;i<exn;i++) a[i]=1ll*a[i]*c[i]%mod;
dft(a,exn,bits,-1);
}
/*
b会被作拓展,但是a不会,所以a数组传入没有任何数组大小的要求
b[exn(n+n-1)]
*/
void poly_inv(int*a,int n,int*b){ // // %(x^n) b(2-ab)
static int c[maxn];
if(n==1) {b[0]=qpow(a[0],mod-2); return;}
poly_inv(a,(n+1)>>1,b);
int exn=1,bits=0;
while(exn<n+n-1) exn<<=1,bits++;
poly_cpy(b,(n+1)>>1,b,exn);
poly_cpy(a,n,c,exn);
dft(b,exn,bits,1);
dft(c,exn,bits,1);
for(int i=0;i<exn;i++) b[i]=b[i]*(2-1ll*c[i]*b[i]%mod+mod)%mod;
dft(b,exn,bits,-1);
}//p4738 开O2 500ms+ 洛谷最快200ms+
/*
a/b=c...d
只有cb有要求exn(2*na-nb)
*/
void poly_div(int*a,int na,int*b,int nb,int*c,int*d){
reverse(a,a+na); reverse(b,b+nb);
poly_inv(b,na-nb+1,c); //exn(2*na-2*nb+1)=exn(2*na-nb-nb+1)
poly_mul(c,na-nb+1,a,na); //exn(na-nb+1+na-1)=exn(2*na-nb)
reverse(a,a+na); reverse(b,b+nb);
reverse(c,c+na-nb+1);
poly_cpy(c,na-nb+1,d,na-nb+1);
poly_mul(d,na-nb+1,b,nb);
for(int i=0;i<na;i++) d[i]=reduce(a[i]-d[i]);
}// p4738 开O2 600ms+ 洛谷最快300ms+
void poly_der(int*a,int n,int*b){ // 微分求导
for(int i=1;i<n;i++) b[i-1]=1ll*a[i]*i%mod;
}
void poly_int(int*a,int n,int*b){ // 默认积分结果项常的数为0
for(int i=1;i<=n;i++) b[i]=1ll*a[i-1]*inv[i]%mod;
b[0]=0;
}
void poly_ln(int*a,int n,int*b){// a[exn(n+n-1)] //保证f(0)=1 , 默认积分结果项常的数为0 ,b[0]=ln(a[0])=1
static int d[maxn];
poly_der(a,n,d); // n-1
poly_inv(a,n,b); // n
poly_mul(d,n-1,b,n);
poly_int(d,n,b); // b[0]=ln(a[0])=ln(1)=0
}//p4725 开O2 700ms+ 洛谷最快300ms+
void poly_exp(int*a,int n,int*b){//a[exn(n+n-1)] // 保证f(0)=0 b(1-ln(b)+f),
static int a_lnb[maxn];
if(n==1) {b[0]=1;return;}
poly_exp(a,(n+1)>>1,b);
poly_cpy(b,(n+1)>>1,b,n);
poly_ln(b,n,a_lnb);
for(int i=0;i<n;++i) a_lnb[i]=reduce(a[i]-a_lnb[i]);
a_lnb[0]=reduce(a_lnb[0]+1-mod);
poly_mul(b,n,a_lnb,n);
}//p4726 开O2 1600ms+ 洛谷最快400ms+
void poly_sqrt(int*a,int n,int*b){//a[exn(n+n-1)] //保证a[0]=1, (b+a/b)/2 比上面那个好一些
static int c[maxn];
if(n==1) {b[0]=1;return;}
poly_sqrt(a,(n+1)>>1,b);
poly_cpy(b,(n+1)>>1,b,n);
poly_inv(b,n,c);
poly_mul(c,n,a,n);
int inv2=qpow(2,mod-2);
for(int i=0;i<n;i++) b[i]=1ll*(b[i]+c[i])*inv2%mod;
}// P5205 开O2 3200+ms 洛谷最快600ms++
int sqrt_residue(int a){ // return sqrt(a)
int b=rand()%mod; //找一个非二次剩余数 b
while(qpow(b,(mod-1)>>1)!=mod-1) b=rand()%mod;
int s=mod-1,t=0; // mod-1 =2^t*s = s<<t
while(!(s&1)) s>>=1,t++;
int inv=qpow(a,mod-2);
int x,k;
for(x=qpow(a,(s+1)>>1),k=1;t-k!=0;k++){ //
int invxx=1ll*inv*x%mod*x%mod;
if(qpow(invxx,1<<(t-k-1))==mod-1){
x=1ll*x*qpow(b,s<<(k-1))%mod;
}
}
return min(x,mod-x);
}
void poly_sqrt_Strong(int*a,int n,int*b){//a[exn(n+n-1)] //强化版 不保证a[0]=1 但保证a[0]为二次剩余
static int c[maxn];
if(n==1) {b[0]=sqrt_residue(a[0]);return;}// 其实就这一行不一样
poly_sqrt_Strong(a,(n+1)>>1,b);
poly_cpy(b,(n+1)>>1,b,n);
poly_inv(b,n,c);
poly_mul(c,n,a,n);
for(int i=0;i<n;i++) b[i]=1ll*(b[i]+c[i])*inv[2]%mod;
}// 开O2跑1900ms+ 洛谷最快800ms++
void poly_pow(int *a,int n,int k,int *b){//a[exn(n+n-1)] // a^k not quick pow but quicker
static int c[maxn],d[maxn];
int t=0;
while(t<n&&a[t]==0) t++;// a[t]*x^t
if(1ll*t*k>=n)return; //k*t 居然能够爆int
a+=t;// a/=x^t
int invat=qpow(a[0],mod-2);
int mul=qpow(a[0],k);
for(int i=0;i<n-t;i++)d[i]=1ll*a[i]*invat%mod;
for(int i=n-t;i<n;i++)d[i]=0;
poly_ln(d,n,c);
for(int i=1;i<n;i++)c[i]=1ll*c[i]*k%mod;
poly_exp(c,n,b);
for(int i=n-1;i>=1ll*t*k;i--) b[i]=1ll*b[i-1ll*k*t]*mul%mod;
for(int i=1ll*k*t-1;i>=0;i--) b[i]=0;
// b*=x^kt
}// 若k为大数,且保证a[0]=1,传入k%mod即可
void poly_sin(int*a,int n,int*b){//a[exn(n+n-1)]// 保证a[0]=0 -> c[0]=0 -> 可以exp
static int c[maxn],d[maxn];
for(int i=0;i<n;i++) c[i]=1ll*a[i]*I%mod;
poly_exp(c,n,b);
poly_inv(b,n,d);
for(int i=0;i<n;i++) b[i]=1ll*(b[i]-d[i]+mod)*inv2I%mod;
} // P5264 开O2 2111ms 洛谷最快691ms
void poly_cos(int*a,int n,int*b){//a[exn(n+n-1)]// 保证a[0]=0 -> c[0]=0 -> 可以exp
static int c[maxn],d[maxn];
for(int i=0;i<n;i++) c[i]=1ll*a[i]*I%mod;
poly_exp(c,n,b);
poly_inv(b,n,d);
for(int i=0;i<n;i++) b[i]=1ll*(b[i]+d[i])*inv[2]%mod;
}// P5264 开O2 2111ms 洛谷最快691ms
void poly_asin(int*a,int n,int*b){//a[exn(n+n-1)]// 保证a[0]=0 积分: f'/(sqrt(1-f*f))
static int c[maxn];
poly_cpy(a,n,b,n);// f
poly_mul(b,n,b,n);// f*f
b[0]=reduce(b[0]-1);// f*f-1
for(int i=0;i<n;i++)b[i]=mod-b[i]; //1-ff
poly_sqrt_Strong(b,n,c);// sqrt(1-ff)
poly_inv(c,n,b); // 1/sqrt(1-ff)
poly_der(a,n,c);// f'
poly_mul(c,n,b,n);//
poly_int(c,n,b);// f'/(sqrt(1-f*f))
}// P5265 开O2 1626ms 洛谷最快985ms
void poly_atan(int*a,int n,int*b){//a[exn(n+n-1)] // 保证a[0]=0 积分: f'/(sqrt(1-f*f))
static int c[maxn];
poly_cpy(a,n,b,n);// f
poly_mul(b,n,b,n);// f*f
b[0]=reduce(b[0]+1-mod);// f*f+1
poly_inv(b,n,c); // 1/(f*f+1)
poly_der(a,n,b);// f'
poly_mul(c,n,b,n);//
poly_int(c,n,b);// f'/(f*f+1)
}// P5265 开O2 1626ms 洛谷最快985ms
int*segtree[maxn<<2],segtreebuf[maxn*lgmaxn];
void zeropoint_to_poly(int rt,int*a,int n){ // T(n)=2*T(n/2)+nlg(n)= nlg(n)
static int t1[maxn];
if(rt==1) segtreebuf[0]=1;// auto ini
segtree[rt]=segtreebuf+segtreebuf[0];
segtreebuf[0]+=n+1;
if(n==1) segtree[rt][0]=mod-a[0],segtree[rt][1]=1;
else{
int mid=n>>1;
zeropoint_to_poly(rt<<1,a,mid);
zeropoint_to_poly(rt<<1|1,a+mid,n-mid);
poly_cpy(segtree[rt<<1],mid+1,t1,mid+1);
poly_mul(t1,mid+1,segtree[rt<<1|1],n-mid+1);
poly_cpy(t1,n+1,segtree[rt],n+1);
}
}
/*
已知x0 x1 x2 ...
已知f(x)=a0x^0+a1x^1+a2x^2+...
求b0=f(x0),f(x1),f(x2)...
zeropoint_to_poly(1,)
*/
void poly_to_point(int rt,int*a,int n,int*b,int bn,int*c){ //T(n)=2*T(n/2)+nlg(n)=nlg(n)
static int buf[maxn<<3],*d=buf,f[maxn];
if(bn<=200&&n<=200){
for(int i=0;i<bn;i++){
c[i]=0;
for(int j=n-1;j>=0;j--) c[i]=(1ll*c[i]*b[i]+a[j])%mod;
}
}
else{
d+=n<<2;
if(n>bn) {
poly_div(a,n,segtree[rt],bn+1,f,d);// nlg(n)
a=d; n=bn;
}
int mid=bn>>1;
poly_to_point(rt<<1,a,n,b,mid,c);
poly_to_point(rt<<1|1,a,n,b+mid,bn-mid,c+mid);
d-=n<<2;
}
}//时间卡边界上,不过刚刚好
// 拉格朗日插值法
int facinv[maxn]={1,1};
void facinv_ini(){
for(int i=0,fac=1;i<maxn;++i,fac=1ll*fac*i%mod) {
facinv[i]=qpow(fac,mod-2);
}
}
int lagrange(int*x,int*y,int n,int var){// y=f(x) 已知f(x0) f(x1) ... f(xn) 计算 f(var) O(n^2)
int b=0;
for(int i=0;i<=n;++i) {
int t1=1,t2=1;
for(int j=0;j<=n;j++){
if(i==j) continue;
t1=1ll*t1*(var -x[j]+mod)%mod;
t2=1ll*t2*(x[i]-x[j]+mod)%mod;
}
int invt2=qpow(t2,mod-2);
b=(b+1ll*y[i]*t1%mod*invt2)%mod;
}
return b;
}
int lagrange(int *y,int n,int x){// O(n) n次多项式有n+1项 y[0]...y[n] -> y[x]
static int prepre[maxn],suf[maxn],*pre=prepre+1;
pre[-1]=suf[n+1]=1;
for(int i=0;i<=n;++i) pre[i]=1ll*pre[i-1]*(x-i+mod)%mod;
for(int i=n;i>=0;i--) suf[i]=1ll*suf[i+1]*(i-x+mod)%mod;
int b=0;
for(int i=0;i<=n;++i) {
int up=1ll*pre[i-1]*suf[i+1]%mod;
int down=1ll*facinv[i]*facinv[n-i]%mod;
b=(b+1ll*y[i]*up%mod*down)%mod;
}
return b;
}
//
void lagrange_dfs(int rt,int*x,int n,int*g,int*a){
static int buf[maxn<<4],*b=buf;
b+=n<<2;
if(n==1) a[0]=g[0];
else{
int mid=n>>1;
lagrange_dfs(rt<<1,x,mid,g,a);
lagrange_dfs(rt<<1|1,x+mid,n-mid,g+mid,b);
poly_mul(a,mid,segtree[rt<<1|1],n-mid+1);
poly_mul(b,n-mid,segtree[rt<<1],mid+1);
for(int i=0;i<n;i++) a[i]=reduce(a[i]+b[i]-mod);
}
b-=n<<2;
}
void lagrange(int*x,int*y,int n,int *a){ // 多项式插值到系数表示法
static int b[maxn],c[maxn]; // yi/gi*pord(x-xj) j!=i
zeropoint_to_poly(1,x,n);
poly_der(segtree[1],n+1,b);// b-> h'
poly_to_point(1,b,n,x,n,c);//c->h'(x) 多点求值 洛必达法则
for(int i=0;i<n;i++) c[i]=1ll*y[i]*qpow(c[i],mod-2)%mod;
zeropoint_to_poly(1,x,n);
lagrange_dfs(1,x,n,c,a);
}
inline int read(){
register int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return (f==1)?x:-x;
}
int a[maxn],b[maxn],c[maxn],x[maxn],y[maxn];
int main(){
dft_ini();inv_ini(); // all_poly
// facinv_ini();// 简单的lagrange需要,复杂的不需要
int n=read();
for(int i=0;i<n;++i) a[i]=read();
poly_ln(a,n,b);
for(int i=0;i<n;i++) printf("%d ",b[i]);
}