这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录 前一修订版 后一修订版 | 前一修订版 | ||
2020-2021:teams:legal_string:jxm2001:多项式_4 [2020/08/23 17:38] jxm2001 |
2020-2021:teams:legal_string:jxm2001:多项式_4 [2020/08/25 17:43] (当前版本) jxm2001 |
||
---|---|---|---|
行 9: | 行 9: | ||
==== 性质 ==== | ==== 性质 ==== | ||
- | 对序列 $A,B$ 做长度为 $n$ 的 $\text{FFT}$ 等价于求序列 $A,B$ 的循环卷积。 | + | 对序列 $A,B$ 做长度为 $n$ 的 $\text{FFT}$ 等价于求序列 $A,B$ 的长度为 $n$ 的循环卷积。 |
考虑单位根反演证明 | 考虑单位根反演证明 | ||
行 25: | 行 25: | ||
事实上普通的卷积计算相当于长度为 $2^n$ 的循环卷积计算,只是循环卷积长度大于 $C$ 序列的长度,所以循环卷积结果即为普通卷积结果。 | 事实上普通的卷积计算相当于长度为 $2^n$ 的循环卷积计算,只是循环卷积长度大于 $C$ 序列的长度,所以循环卷积结果即为普通卷积结果。 | ||
+ | |||
+ | 值得一提的是,循环卷积的求逆、快速幂等操作直接对 $\text{DFT}$ 的点值进行相应运算即可。 | ||
+ | |||
+ | 另外注意到 $f(w^{-k})=f(w^{n-k})$,于是循环卷积的 $\text{IDFT}$ 过程可以通过将序列 $[1,n-1]$ 部分翻转再 $\text{DFT}$ 的方式实现。 | ||
+ | |||
+ | ==== Cooley–Tukey FFT algorithm ==== | ||
+ | |||
+ | === 算法实现 === | ||
+ | |||
+ | 普通 $\text{DFT}$ 过程是将序列根据奇偶幂次分成两段经行递归,现考虑将序列分成 $d$ 段进行递归,设 | ||
+ | |||
+ | $$f(x)=a_0+a_1x^1+a_2x^2+\cdots a_{n-1}x^{n-1},f_k(x)=a_kx^k+a_{d+k}x^{d+k}+a_{2d+k}x^{2d+k}+\cdots+a_{n-d+k}x^{n-d+k}(0\le k\lt d),m=\frac nd$$ | ||
+ | |||
+ | 于是有 | ||
+ | |||
+ | $$f(w_n^{im+j})=\sum_{k=0}^{d-1}w_n^{(im+j)k}f_k(w_n^{(im+j)d})=\sum_{k=0}^{d-1}w_n^{(im+j)k}f_k(w_n^{in}w_n^{jd})=\sum_{k=0}^{d-1}w_n^{(im+j)k}f_k(w_m^j)$$ | ||
+ | |||
+ | 计算出每个点值的时间为 $O(d)$,每层共 $O(n)$ 个点值。设 $n=p_1^{\alpha_1}p_2^{\alpha_2}\cdots p_k^{\alpha_k}$,于是总时间复杂度 $O\left(n\sum_{i=1}^k p_i\alpha_i\right)$。 | ||
+ | |||
+ | === 算法例题 === | ||
+ | |||
+ | [[https://www.luogu.com.cn/problem/P4191|洛谷p4191]] | ||
+ | |||
+ | 给定长度为 $n$ 的序列 $A,B$,计算 $AB^C$ 的长度为 $n$ 的循环卷积模 $n+1$ 意义下的值。 | ||
+ | |||
+ | 数据保证 $n+1$ 为素数,$n\le 5\times 10^5$,$n$ 的最大质因子不超过 $10$。 | ||
+ | |||
+ | == 题解 == | ||
+ | |||
+ | $\text{Cooley–Tukey FFT algorithm}$ 板子题,$\text{DFT}$ 后直接对点值快速幂即可,时间复杂度 $O(7n\log n)$ | ||
+ | |||
+ | <hidden 查看代码> | ||
+ | <code cpp> | ||
+ | const int MAXN=5e5+5,MAXM=20,MAX_div=7; | ||
+ | int p; | ||
+ | int quick_pow(int a,int b){ | ||
+ | int ans=1; | ||
+ | while(b){ | ||
+ | if(b&1)ans=1LL*ans*a%p; | ||
+ | a=1LL*a*a%p; | ||
+ | b>>=1; | ||
+ | } | ||
+ | return ans; | ||
+ | } | ||
+ | vector<int> pdiv,frac,Wn[MAXM]; | ||
+ | bool check(int x){ | ||
+ | _for(i,0,pdiv.size()){ | ||
+ | if(quick_pow(x,(p-1)/pdiv[i])==1) | ||
+ | return false; | ||
+ | } | ||
+ | return true; | ||
+ | } | ||
+ | void get_G(int n){ | ||
+ | int temp=p-1,g; | ||
+ | for(int i=2;i*i<=temp;i++){ | ||
+ | if(temp%i==0){ | ||
+ | pdiv.push_back(i); | ||
+ | while(temp%i==0)temp/=i; | ||
+ | } | ||
+ | } | ||
+ | if(temp!=1)pdiv.push_back(temp); | ||
+ | _for(i,2,p){ | ||
+ | if(check(i)){ | ||
+ | g=i; | ||
+ | break; | ||
+ | } | ||
+ | } | ||
+ | temp=n; | ||
+ | for(int i=2;i*i<=temp;i++){ | ||
+ | while(temp%i==0){ | ||
+ | frac.push_back(i); | ||
+ | temp/=i; | ||
+ | } | ||
+ | } | ||
+ | if(temp!=1)frac.push_back(temp); | ||
+ | int len=n,wn; | ||
+ | _for(i,0,frac.size()){ | ||
+ | Wn[i].resize(len); | ||
+ | wn=quick_pow(g,(p-1)/len),Wn[i][0]=1; | ||
+ | _for(j,1,len)Wn[i][j]=1LL*Wn[i][j-1]*wn%p; | ||
+ | len/=frac[i]; | ||
+ | } | ||
+ | } | ||
+ | int temp[MAXN]; | ||
+ | void DFT(int *f,int n,int dep=0){ | ||
+ | if(n==1)return; | ||
+ | memcpy(temp,f,sizeof(int)*n); | ||
+ | int m=n/frac[dep],d=frac[dep],*g[MAX_div]; | ||
+ | _for(i,0,d)g[i]=f+i*m; | ||
+ | for(int i=0,k=0;i<n;i+=d,k++){ | ||
+ | _for(j,0,d) | ||
+ | g[j][k]=temp[i+j]; | ||
+ | } | ||
+ | _for(i,0,d)DFT(g[i],m,dep+1); | ||
+ | _for(i,0,d){ | ||
+ | _for(j,0,m){ | ||
+ | int pos=i*m+j; | ||
+ | temp[pos]=0; | ||
+ | _for(k,0,d) | ||
+ | temp[pos]=(temp[pos]+1LL*Wn[dep][pos*k%n]*g[k][j])%p; | ||
+ | } | ||
+ | } | ||
+ | memcpy(f,temp,sizeof(int)*n); | ||
+ | } | ||
+ | void IDFT(int *f,int n){ | ||
+ | reverse(f+1,f+n); | ||
+ | DFT(f,n); | ||
+ | int div=quick_pow(n,p-2); | ||
+ | _for(i,0,n)f[i]=1LL*f[i]*div%p; | ||
+ | } | ||
+ | int a[MAXN],b[MAXN]; | ||
+ | int main() | ||
+ | { | ||
+ | int n=read_int(),k=read_int(); | ||
+ | p=n+1; | ||
+ | get_G(n); | ||
+ | _for(i,0,n)a[i]=read_int(); | ||
+ | _for(i,0,n)b[i]=read_int(); | ||
+ | DFT(a,n);DFT(b,n); | ||
+ | _for(i,0,n)a[i]=1LL*a[i]*quick_pow(b[i],k)%p; | ||
+ | IDFT(a,n); | ||
+ | _for(i,0,n)enter(a[i]); | ||
+ | return 0; | ||
+ | } | ||
+ | </code> | ||
+ | </hidden> | ||
==== Bluestein's Algotithm ==== | ==== Bluestein's Algotithm ==== | ||
+ | |||
+ | === 算法实现 === | ||
考虑 $\text{DFT}$ 过程,有 | 考虑 $\text{DFT}$ 过程,有 | ||
行 38: | 行 166: | ||
$$ | $$ | ||
- | 其中 ${n\choose 2}$ 表示组合数。易知上式可以通过普通卷积求解,$\text{IDFT}$ 过程类似。 | + | 其中 ${n\choose 2}$ 表示组合数,易知上式可以通过普通卷积求解。 |
- | 值得一提的是,循环卷积的求逆、快速幂等操作直接对 $\text{DFT}$ 的点值进行相应运算即可。 | + | 考虑 $\text{IDFT}$ 过程,同样有 |
- | ==== 算法例题 ==== | + | $$ |
+ | \begin{equation}\begin{split} | ||
+ | a_k&=\frac 1n\sum_{i=0}^{n-1}y_iw_n^{-ki}\\ | ||
+ | &=\frac 1n\sum_{i=0}^{n-1}y_iw_n^{-{k+i\choose 2}+{k\choose 2}+{i\choose 2}}\\ | ||
+ | &=\frac 1nw_n^{{k\choose 2}}\sum_{i=0}^{n-1}\left(y_iw_n^{{i\choose 2}}\right)w_n^{-{k+i\choose 2}} | ||
+ | \end{split}\end{equation} | ||
+ | $$ | ||
+ | |||
+ | $\text{ }$ | ||
+ | |||
+ | === 算法例题 === | ||
+ | |||
+ | [[https://www.luogu.com.cn/problem/P5293|洛谷p5293]] | ||
+ | |||
+ | 给定一个二维 $[L+1]\times n$ 的空间,其中 $(u_1,v_1)\to (u_2,v_2)$ 有 $w_{v_1,v_2}$ 条重边。 | ||
+ | |||
+ | 假设起点为 $(0,x)$,终点为 $(\ast,y)$($\ast$ 为任意值),路径长度 $m$ 定义为路径的边数。 | ||
+ | |||
+ | 对每个 $0\le t\lt k$,求满足所有 $m\equiv t\pmod k$ 且横坐标单增的路径数模 $p$ 意义下的值。 | ||
+ | |||
+ | 数据保证 $p$ 为素数,$10^8\le p\le 2^{30},k\mid p-1,1\le k\lt 65536,1\le n\le 3,L\le 10^9$。 | ||
+ | |||
+ | == 题解 == | ||
+ | |||
+ | 假设 $f_{a,b}$ 表示 $m=a$ 且 $y=b$ 的路径数,$g_{a,b}$ 表示将空间的 $X$ 维消去后 $m=a$ 且 $y=b$ 的路径数。 | ||
+ | |||
+ | 于是有状态转移方程 | ||
+ | |||
+ | $$g_{a,b}=\sum_{i=1}^n g_{a-1,i}w_{i,b}\text{ },\text{ }f_{a,b}={L\choose a}g_{a,b}$$ | ||
+ | |||
+ | 设矩阵 | ||
+ | |||
+ | $$W=\begin{bmatrix}w_{1,1} & \cdots & w_{1,n} \\ | ||
+ | \vdots &\ddots & \vdots \\ | ||
+ | w_{n,1} &\cdots & w_{n,n}\end{bmatrix}$$ | ||
+ | |||
+ | 设 $G_i=(g_{i,1}\cdots g_{i,n})$,于是有 $G_i=G_0W^i$。 | ||
+ | |||
+ | 考虑单位根反演,设 $w_k\equiv g^{\frac {p-1}k}\pmod p,g$ 为 $p$ 的原根,有 | ||
+ | |||
+ | $$ | ||
+ | \begin{equation}\begin{split} | ||
+ | \text{ans}_t&=\sum_{i=0}^Lf_{i,y}[i\bmod k=t]\\ | ||
+ | &=\frac 1k\sum_{i=0}^Lf_{i,y}\sum_{j=0}^{k-1}w_k^{(i-t)j}\\ | ||
+ | &=\frac 1k\sum_{j=0}^{k-1}w_k^{-tj}\sum_{i=0}^L f_{i,y}w_k^{ij} | ||
+ | \end{split}\end{equation} | ||
+ | $$ | ||
+ | |||
+ | 根据二项式定理,有 | ||
+ | |||
+ | $$\sum_{i=0}^L w_k^{ij}(f_{i,1}\cdots f_{i,n})=\sum_{i=0}^L w_k^{ij}{L\choose i}(g_{i,1}\cdots g_{i,n})=G_0\sum_{i=0}^L {L\choose i}\left(w_k^jW\right)^i=G_0\left(w_k^jW+I\right)^L$$ | ||
+ | |||
+ | 于是根据矩阵快速幂可以 $O(kn^3\log L)$ 计算出所有 $h_j=\sum_{i=0}^L f_{i,y}w_k^{ij}(0\le j\lt k)$。 | ||
+ | |||
+ | 于是有 | ||
+ | |||
+ | $$\text{ans}_t=\frac 1k\sum_{i=0}^{k-1}w_k^{-ti}h_i$$ | ||
+ | |||
+ | 发现上式就是 $\text{Bluestein's Algotithm}$ 的 $\text{IDFT}$ 过程,直接求解时间复杂度 $O(k\log k)$。 | ||
+ | |||
+ | 总时间复杂度 $O(kn^3\log L)$,主要用于计算矩阵快速幂。 | ||
+ | |||
+ | <hidden 查看代码> | ||
+ | <code cpp> | ||
+ | const int MAXN=1<<16|5; | ||
+ | const long double pi=acos(-1.0); | ||
+ | int p,sz,gw[MAXN]; | ||
+ | int quick_pow(int a,int b){ | ||
+ | int ans=1; | ||
+ | while(b){ | ||
+ | if(b&1)ans=1LL*ans*a%p; | ||
+ | a=1LL*a*a%p; | ||
+ | b>>=1; | ||
+ | } | ||
+ | return ans; | ||
+ | } | ||
+ | vector<int> pdiv; | ||
+ | bool check(int x){ | ||
+ | _for(i,0,pdiv.size()){ | ||
+ | if(quick_pow(x,(p-1)/pdiv[i])==1) | ||
+ | return false; | ||
+ | } | ||
+ | return true; | ||
+ | } | ||
+ | void get_G(int k){ | ||
+ | int temp=p-1,g; | ||
+ | for(int i=2;i*i<=temp;i++){ | ||
+ | if(temp%i==0){ | ||
+ | pdiv.push_back(i); | ||
+ | while(temp%i==0)temp/=i; | ||
+ | } | ||
+ | } | ||
+ | if(temp!=1)pdiv.push_back(temp); | ||
+ | _for(i,2,p){ | ||
+ | if(check(i)){ | ||
+ | g=i; | ||
+ | break; | ||
+ | } | ||
+ | } | ||
+ | int w=quick_pow(g,(p-1)/k); | ||
+ | gw[0]=1; | ||
+ | _rep(i,1,k)gw[i]=1LL*gw[i-1]*w%p; | ||
+ | } | ||
+ | struct Matrix{ | ||
+ | int ele[3][3]; | ||
+ | Matrix(int x=0){ | ||
+ | mem(ele,0); | ||
+ | _for(i,0,sz)ele[i][i]=x; | ||
+ | } | ||
+ | Matrix operator + (const Matrix b){ | ||
+ | Matrix c; | ||
+ | _for(i,0,sz) | ||
+ | _for(j,0,sz) | ||
+ | c.ele[i][j]=(ele[i][j]+b.ele[i][j])%p; | ||
+ | return c; | ||
+ | } | ||
+ | Matrix operator * (const int b){ | ||
+ | Matrix c; | ||
+ | _for(i,0,sz) | ||
+ | _for(j,0,sz) | ||
+ | c.ele[i][j]=1LL*ele[i][j]*b%p; | ||
+ | return c; | ||
+ | } | ||
+ | void operator *= (const Matrix b){ | ||
+ | Matrix c=*this; | ||
+ | _for(i,0,sz) | ||
+ | _for(j,0,sz){ | ||
+ | ele[i][j]=0; | ||
+ | _for(k,0,sz) | ||
+ | ele[i][j]=(ele[i][j]+1LL*c.ele[i][k]*b.ele[k][j])%p; | ||
+ | } | ||
+ | } | ||
+ | }; | ||
+ | Matrix quick_pow(const Matrix &a,int k){ | ||
+ | Matrix Ans(1),Base; | ||
+ | Base=a; | ||
+ | while(k){ | ||
+ | if(k&1)Ans*=Base; | ||
+ | k>>=1; | ||
+ | Base*=Base; | ||
+ | } | ||
+ | return Ans; | ||
+ | } | ||
+ | struct complex{ | ||
+ | long double x,y; | ||
+ | complex(long double x=0.0,long double y=0.0):x(x),y(y){} | ||
+ | complex operator + (const complex &b){ | ||
+ | return complex(x+b.x,y+b.y); | ||
+ | } | ||
+ | complex operator - (const complex &b){ | ||
+ | return complex(x-b.x,y-b.y); | ||
+ | } | ||
+ | complex operator * (const complex &b){ | ||
+ | return complex(x*b.x-y*b.y,x*b.y+y*b.x); | ||
+ | } | ||
+ | }; | ||
+ | int rev[MAXN<<2]; | ||
+ | int build(int k){ | ||
+ | int n,pos=0; | ||
+ | while((1<<pos)<=k)pos++; | ||
+ | n=1<<pos; | ||
+ | _for(i,0,n)rev[i]=(rev[i>>1]>>1)|((i&1)<<(pos-1)); | ||
+ | return n; | ||
+ | } | ||
+ | void FFT(complex *f,int n,int type){ | ||
+ | _for(i,0,n)if(i<rev[i]) | ||
+ | swap(f[i],f[rev[i]]); | ||
+ | complex t1,t2; | ||
+ | for(int i=1;i<n;i<<=1){ | ||
+ | complex w(cos(pi/i),type*sin(pi/i)); | ||
+ | for(int j=0;j<n;j+=(i<<1)){ | ||
+ | complex cur(1.0,0.0); | ||
+ | _for(k,j,j+i){ | ||
+ | t1=f[k],t2=cur*f[k+i]; | ||
+ | f[k]=t1+t2,f[k+i]=t1-t2; | ||
+ | cur=cur*w; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | if(type==-1)_for(i,0,n) | ||
+ | f[i].x/=n,f[i].y/=n; | ||
+ | } | ||
+ | void FFT2(complex *f1,complex *f2,int n){ | ||
+ | FFT(f1,n,1); | ||
+ | f2[0].x=f1[0].x,f2[0].y=-f1[0].y; | ||
+ | _for(i,1,n) | ||
+ | f2[i].x=f1[n-i].x,f2[i].y=-f1[n-i].y; | ||
+ | complex t1,t2; | ||
+ | _for(i,0,n){ | ||
+ | t1=f1[i],t2=f2[i]; | ||
+ | f1[i]=complex((t1.x+t2.x)*0.5,(t1.y+t2.y)*0.5); | ||
+ | f2[i]=complex((t1.y-t2.y)*0.5,(t2.x-t1.x)*0.5); | ||
+ | } | ||
+ | } | ||
+ | void MTT(int *f,int n1,int *g,int n2,int *ans,int mod){ | ||
+ | static complex f1[MAXN<<2],f2[MAXN<<2],g1[MAXN<<2],g2[MAXN<<2],temp[2][MAXN<<2]; | ||
+ | int n=build(n1+n2),m=sqrt(mod)+1; | ||
+ | _rep(i,0,n1)f1[i].x=f[i]/m,f1[i].y=f[i]%m; | ||
+ | _rep(i,0,n2)g1[i].x=g[i]/m,g1[i].y=g[i]%m; | ||
+ | FFT2(f1,f2,n);FFT2(g1,g2,n); | ||
+ | complex I(0.0,1.0); | ||
+ | _for(i,0,n){ | ||
+ | temp[0][i]=f1[i]*g1[i]+I*f2[i]*g1[i]; | ||
+ | temp[1][i]=f1[i]*g2[i]+I*f2[i]*g2[i]; | ||
+ | } | ||
+ | FFT(temp[0],n,-1);FFT(temp[1],n,-1); | ||
+ | LL a,b,c; | ||
+ | _rep(i,0,n1+n2){ | ||
+ | a=temp[0][i].x+0.5,b=temp[0][i].y+temp[1][i].x+0.5,c=temp[1][i].y+0.5; | ||
+ | ans[i]=((a%mod*m%mod*m%mod+b%mod*m%mod+c%mod)%mod+mod)%mod; | ||
+ | } | ||
+ | } | ||
+ | Matrix W; | ||
+ | int a[MAXN<<2],b[MAXN<<2],c[MAXN<<2]; | ||
+ | int main() | ||
+ | { | ||
+ | int k,L,x,y; | ||
+ | sz=read_int(),k=read_int(),L=read_int(),x=read_int()-1,y=read_int()-1,p=read_int(); | ||
+ | get_G(k); | ||
+ | _for(i,0,sz) | ||
+ | _for(j,0,sz) | ||
+ | W.ele[i][j]=read_int(); | ||
+ | _for(i,0,k){ | ||
+ | Matrix temp=quick_pow(W*gw[i]+Matrix(1),L); | ||
+ | a[i]=1LL*temp.ele[x][y]*gw[1LL*i*(i-1)/2%k]%p; | ||
+ | } | ||
+ | _for(i,0,2*k-1)b[i]=gw[k-1LL*i*(i-1)/2%k]%p; | ||
+ | reverse(a,a+k); | ||
+ | MTT(a,k-1,b,2*k-2,c,p); | ||
+ | int div=quick_pow(k,p-2); | ||
+ | _for(i,0,k) | ||
+ | enter(1LL*div*gw[1LL*i*(i-1)/2%k]%p*c[k-1+i]%p); | ||
+ | return 0; | ||
+ | } | ||
+ | </code> | ||
+ | </hidden> | ||
+ | |||
+ | ===== 常系数齐次线性递推 ===== | ||
+ | |||
+ | ==== 算法简介 ==== | ||
+ | |||
+ | 给定 $a_n=f_1a_{n-1}+f_2a_{n-2}+\cdots +f_ka_{n-k}$ 以及 $a_0,a_1\cdots a_{k-1}$,询问 $a_n$ 的值。时间复杂度 $O(k\log k\log n)$。 | ||
+ | |||
+ | ==== 算法实现 ==== | ||
+ | |||
+ | [[https://www.luogu.com.cn/problem/P4723|洛谷p4723]] | ||
+ | |||
+ | 考虑求斐波那契数列过程,$f_5=f_4+f_3=2f_3+f_2=3f_2+2f_1=5f_1+3f_0$。 | ||
+ | |||
+ | 从多项式角度上考虑该过程,$f_n=f_{n-1}+f_{n-2}$ 的特征多项式为 $x^2-x-1$,并且有 | ||
+ | |||
+ | $$ | ||
+ | \begin{matrix} | ||
+ | &0x^0&+0x^1&+0x^2&+0x^3&+0x^4&+1x^5\\ | ||
+ | \equiv&0x^0&+0x^1&+0x^2&+1x^3&+1x^4&+0x^5\\ | ||
+ | \equiv&0x^0&+0x^1&+1x^2&+2x^3&+0x^4&+0x^5\\ | ||
+ | \equiv&0x^0&+3x^1&+2x^2&+0x^3&+0x^4&+0x^5\\ | ||
+ | \equiv&3x^0&+5x^1&+0x^2&+0x^3&+0x^4&+0x^5&\pmod {x^2-x-1} | ||
+ | \end{matrix} | ||
+ | $$ | ||
+ | |||
+ | 于是 $f_5=\left(x^5\bmod {x^2-x-1}\right)\cdot (f_0,f_1)=(3,5)\cdot (f_0,f_1)$。 | ||
+ | |||
+ | 以此类推,对 $a_n=f_1a_{n-1}+f_2a_{n-2}+\cdots +f_ka_{n-k}$,其特征多项式为 $x^k-f_1x^{k-1}-f_2x^{k-2}-\cdots -f_k$。 | ||
+ | |||
+ | 于是 $a_n= \left(x^n\bmod {x^k-f_1x^{k-1}-f_2x^{k-2}-\cdots -f_k}\right)\cdot (a_0,a_1\cdots a_{k-1})$。 | ||
+ | |||
+ | 其中 $\left(x^n\bmod {x^k-f_1x^{k-1}-f_2x^{k-2}-\cdots -f_k}\right)$ 可以通过快速幂与多项式带余除法 $O(k\log k\log n)$ 计算。 | ||
+ | |||
+ | 发现每次带余除法计算时 $g(x)$ 都是固定的,可以考虑先计算出 $\frac 1{g^R(x)}$ 减小常数。 | ||
+ | |||
+ | <hidden 查看代码> | ||
+ | <code cpp> | ||
+ | const int MAXN=64005,Mod=998244353; | ||
+ | int quick_pow(int a,int b){ | ||
+ | int ans=1; | ||
+ | while(b){ | ||
+ | if(b&1) | ||
+ | ans=1LL*ans*a%Mod; | ||
+ | a=1LL*a*a%Mod; | ||
+ | b>>=1; | ||
+ | } | ||
+ | return ans; | ||
+ | } | ||
+ | namespace Poly{ | ||
+ | const int G=3; | ||
+ | int rev[MAXN<<2],Pool[MAXN<<3],*Wn[30]; | ||
+ | void init(){ | ||
+ | int lg2=0,*pos=Pool,n,w; | ||
+ | while((1<<lg2)<MAXN*2)lg2++; | ||
+ | n=1<<lg2,w=quick_pow(G,(Mod-1)/(1<<lg2)); | ||
+ | while(~lg2){ | ||
+ | Wn[lg2]=pos,pos+=n; | ||
+ | Wn[lg2][0]=1; | ||
+ | _for(i,1,n)Wn[lg2][i]=1LL*Wn[lg2][i-1]*w%Mod; | ||
+ | w=1LL*w*w%Mod; | ||
+ | lg2--;n>>=1; | ||
+ | } | ||
+ | } | ||
+ | int build(int k){ | ||
+ | int n,pos=0; | ||
+ | while((1<<pos)<=k)pos++; | ||
+ | n=1<<pos; | ||
+ | _for(i,0,n)rev[i]=(rev[i>>1]>>1)|((i&1)<<(pos-1)); | ||
+ | return n; | ||
+ | } | ||
+ | void NTT(int *f,int n,bool type){ | ||
+ | _for(i,0,n)if(i<rev[i]) | ||
+ | swap(f[i],f[rev[i]]); | ||
+ | int t1,t2; | ||
+ | for(int i=1,lg2=1;i<n;i<<=1,lg2++){ | ||
+ | for(int j=0;j<n;j+=(i<<1)){ | ||
+ | _for(k,j,j+i){ | ||
+ | t1=f[k],t2=1LL*Wn[lg2][k-j]*f[k+i]%Mod; | ||
+ | f[k]=(t1+t2)%Mod,f[k+i]=(t1-t2)%Mod; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | if(!type){ | ||
+ | reverse(f+1,f+n); | ||
+ | int div=quick_pow(n,Mod-2); | ||
+ | _for(i,0,n)f[i]=(1LL*f[i]*div%Mod+Mod)%Mod; | ||
+ | } | ||
+ | } | ||
+ | void Mul(int *f,int _n,int *g,int _m,int xmod=0){ | ||
+ | int n=build(_n+_m-2); | ||
+ | _for(i,_n,n)f[i]=0;_for(i,_m,n)g[i]=0; | ||
+ | NTT(f,n,true);NTT(g,n,true); | ||
+ | _for(i,0,n)f[i]=1LL*f[i]*g[i]%Mod; | ||
+ | NTT(f,n,false); | ||
+ | if(xmod)_for(i,xmod,n)f[i]=0; | ||
+ | } | ||
+ | void Inv(const int *f,int *g,int _n){ | ||
+ | static int temp[MAXN<<2]; | ||
+ | if(_n==1)return g[0]=quick_pow(f[0],Mod-2),void(); | ||
+ | Inv(f,g,(_n+1)>>1); | ||
+ | int n=build((_n-1)<<1); | ||
+ | _for(i,(_n+1)>>1,n)g[i]=0; | ||
+ | _for(i,0,_n)temp[i]=f[i];_for(i,_n,n)temp[i]=0; | ||
+ | NTT(g,n,true);NTT(temp,n,true); | ||
+ | _for(i,0,n)g[i]=(2-1LL*temp[i]*g[i]%Mod)*g[i]%Mod; | ||
+ | NTT(g,n,false); | ||
+ | _for(i,_n,n)g[i]=0; | ||
+ | } | ||
+ | void Div(int *f,int _n,const int *g,int _m,int *gR){ | ||
+ | static int temp[MAXN<<2],q[MAXN<<2],r[MAXN<<2]; | ||
+ | _rep(i,0,_n-_m)q[i]=gR[i]; | ||
+ | _rep(i,0,_n)temp[i]=f[_n-i]; | ||
+ | Mul(q,_n-_m+1,temp,_n+1,_n-_m+1); | ||
+ | for(int i=0,j=_n-_m;i<j;i++,j--)swap(q[i],q[j]); | ||
+ | int __m=min(_n-_m+1,_m); | ||
+ | _for(i,0,_m)r[i]=g[i];_for(i,0,__m)temp[i]=q[i]; | ||
+ | Mul(r,_m,temp,__m,_m); | ||
+ | _for(i,0,_m)f[i]=(f[i]+Mod-r[i])%Mod; | ||
+ | } | ||
+ | void Pow_2(int *f,int _n,const int *g,int _m){ | ||
+ | static int temp1[MAXN<<2],temp2[MAXN<<2]; | ||
+ | _rep(i,0,_m)temp1[i]=g[_m-i]; | ||
+ | Inv(temp1,temp2,_m-1); | ||
+ | mem(temp1,0); | ||
+ | temp1[0]=1; | ||
+ | while(_n){ | ||
+ | int n=build(_m+_m-2); | ||
+ | _for(i,_m,n)f[i]=0; | ||
+ | NTT(f,n,true); | ||
+ | if(_n&1){ | ||
+ | _for(i,_m,n)temp1[i]=0; | ||
+ | NTT(temp1,n,true); | ||
+ | _for(i,0,n)temp1[i]=1LL*f[i]*temp1[i]%Mod; | ||
+ | NTT(temp1,n,false); | ||
+ | } | ||
+ | _for(i,0,n)f[i]=1LL*f[i]*f[i]%Mod; | ||
+ | NTT(f,n,false); | ||
+ | if(_n&1)Div(temp1,_m+_m-2,g,_m,temp2); | ||
+ | Div(f,_m+_m-2,g,_m,temp2); | ||
+ | _n>>=1; | ||
+ | } | ||
+ | mem(f,0); | ||
+ | _for(i,0,_m)f[i]=temp1[i]; | ||
+ | } | ||
+ | } | ||
+ | int f[MAXN<<2],g[MAXN<<2],a[MAXN]; | ||
+ | int main() | ||
+ | { | ||
+ | Poly::init(); | ||
+ | int n=read_int(),k=read_int(); | ||
+ | g[k]=1; | ||
+ | for(int i=k-1;i>=0;i--)g[i]=-read_int(); | ||
+ | _for(i,0,k)a[i]=read_int(); | ||
+ | f[1]=1; | ||
+ | Poly::Pow_2(f,n,g,k); | ||
+ | int ans=0; | ||
+ | _for(i,0,k)ans=(ans+1LL*f[i]*a[i])%Mod; | ||
+ | enter((ans+Mod)%Mod); | ||
+ | return 0; | ||
+ | } | ||
+ | </code> | ||
+ | </hidden> | ||
+ | |||
+ | ===== 多项式多点求值 ===== | ||
+ | |||
+ | ==== 算法简介 ==== | ||
+ | |||
+ | 给定一个 $n$ 次多项式 $f(x)$,求 $f(a_i)(1\le i\le m)$。时间复杂度 $O(n\log n+m\log^2 m)$,空间复杂度 $O(m\log m)$。 | ||
+ | |||
+ | ==== 算法实现 ==== | ||
+ | |||
+ | [[https://www.luogu.com.cn/problem/P5050|洛谷p5050]] | ||
+ | |||
+ | 设 $f(x)=c_0+c_1x+c_2x^2\cdots c_nx^n$,于是有 | ||
+ | |||
+ | $$ | ||
+ | \begin{equation}\begin{split} | ||
+ | f(x)-f(x_0)&\equiv c_0-c_0+c_1(x-x_0)+c_2(x^2-x_0^2)\cdots +c_n(x^n-x_0^n)\\ | ||
+ | &\equiv c_1(x-x_0)+c_2(x-x_0)(x+x_0)\cdots +c_n(x-x_0)(x^{n-1}+x^{n-2}x_0+\cdots +x_0^{n-1})\\ | ||
+ | &\equiv 0\pmod {x-x_0} | ||
+ | \end{split}\end{equation} | ||
+ | $$ | ||
+ | |||
+ | 即 $f(x_0)\equiv f(x)\pmod {x-x_0}$,于是如果能快速计算 $f(x)\bmod {x-x_0}$,就可以得到 $f(x_0)$。 | ||
+ | |||
+ | 记 $f_{l,r}(x)\equiv f(x)\pmod {\prod_{i=l}^r (x-a_i)}$,于是有 $f_{l,r}(x)\equiv f_{l,m}(x)\pmod{\prod_{i=l}^m (x-a_i)},f_{l,r}(x)\equiv f_{m+1,r}(x)\pmod{\prod_{i=m+1}^r (x-a_i)}$。 | ||
+ | |||
+ | 考虑 $O(m\log^2 m)$ 自底向上建立线段树,每个节点存储 $\prod_{i=l}^r (x-a_i)$ 的多项式。 | ||
+ | |||
+ | 然后 $O(m\log^2 m)$ 自顶向下进行带余除法,跑到叶子节点就可以得到每个 $f(a_i)$ 的值。 | ||
+ | |||
+ | 注意根节点需要提前进行带余除法,同时该算法常数较大,考虑常数优化。 | ||
+ | |||
+ | 不妨令 $r-l$ 小于某个固定值 $L$ 后采用 $O(L^2)$ 暴力秦九韶展开,秦九韶复杂度为 $O(\cfrac mLL^2)=O(mL)$。 | ||
+ | |||
+ | 于是总时间复杂变为 $O(n\log n+m\log m(\log m-\log L)+mL)$,经检验 $m=64000$ 时取 $L=640$ 效果较佳。 | ||
+ | |||
+ | <hidden 查看代码> | ||
+ | <code cpp> | ||
+ | const int MAXN=64005,MINL=640,Mod=998244353; | ||
+ | int quick_pow(int a,int b){ | ||
+ | int ans=1; | ||
+ | while(b){ | ||
+ | if(b&1) | ||
+ | ans=1LL*ans*a%Mod; | ||
+ | a=1LL*a*a%Mod; | ||
+ | b>>=1; | ||
+ | } | ||
+ | return ans; | ||
+ | } | ||
+ | namespace Poly{ | ||
+ | const int G=3; | ||
+ | int rev[MAXN<<2],Pool[MAXN<<3],*Wn[30]; | ||
+ | void init(){ | ||
+ | int lg2=0,*pos=Pool,n,w; | ||
+ | while((1<<lg2)<MAXN*2)lg2++; | ||
+ | n=1<<lg2,w=quick_pow(G,(Mod-1)/(1<<lg2)); | ||
+ | while(~lg2){ | ||
+ | Wn[lg2]=pos,pos+=n; | ||
+ | Wn[lg2][0]=1; | ||
+ | _for(i,1,n)Wn[lg2][i]=1LL*Wn[lg2][i-1]*w%Mod; | ||
+ | w=1LL*w*w%Mod; | ||
+ | lg2--;n>>=1; | ||
+ | } | ||
+ | } | ||
+ | int build(int k){ | ||
+ | int n,pos=0; | ||
+ | while((1<<pos)<=k)pos++; | ||
+ | n=1<<pos; | ||
+ | _for(i,0,n)rev[i]=(rev[i>>1]>>1)|((i&1)<<(pos-1)); | ||
+ | return n; | ||
+ | } | ||
+ | void NTT(int *f,int n,bool type){ | ||
+ | _for(i,0,n)if(i<rev[i]) | ||
+ | swap(f[i],f[rev[i]]); | ||
+ | int t1,t2; | ||
+ | for(int i=1,lg2=1;i<n;i<<=1,lg2++){ | ||
+ | for(int j=0;j<n;j+=(i<<1)){ | ||
+ | _for(k,j,j+i){ | ||
+ | t1=f[k],t2=1LL*Wn[lg2][k-j]*f[k+i]%Mod; | ||
+ | f[k]=(t1+t2)%Mod,f[k+i]=(t1-t2)%Mod; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | if(!type){ | ||
+ | reverse(f+1,f+n); | ||
+ | int div=quick_pow(n,Mod-2); | ||
+ | _for(i,0,n)f[i]=(1LL*f[i]*div%Mod+Mod)%Mod; | ||
+ | } | ||
+ | } | ||
+ | void Mul(int *f,int _n,int *g,int _m,int xmod=0){ | ||
+ | int n=build(_n+_m-2); | ||
+ | _for(i,_n,n)f[i]=0;_for(i,_m,n)g[i]=0; | ||
+ | NTT(f,n,true);NTT(g,n,true); | ||
+ | _for(i,0,n)f[i]=1LL*f[i]*g[i]%Mod; | ||
+ | NTT(f,n,false); | ||
+ | if(xmod)_for(i,xmod,n)f[i]=0; | ||
+ | } | ||
+ | void Mul2(const int *f,int _n,const int *g,int _m,int *h){ | ||
+ | static int temp1[MAXN<<2],temp2[MAXN<<2]; | ||
+ | int n=build(_n+_m-2); | ||
+ | memcpy(temp1,f,sizeof(int)*_n);memcpy(temp2,g,sizeof(int)*_m); | ||
+ | _for(i,_n,n)temp1[i]=0;_for(i,_m,n)temp2[i]=0; | ||
+ | NTT(temp1,n,true);NTT(temp2,n,true); | ||
+ | _for(i,0,n)temp1[i]=1LL*temp1[i]*temp2[i]%Mod; | ||
+ | NTT(temp1,n,false); | ||
+ | n=_n+_m-1; | ||
+ | _for(i,0,n)h[i]=temp1[i]; | ||
+ | } | ||
+ | void Inv(const int *f,int *g,int _n){ | ||
+ | static int temp[MAXN<<2]; | ||
+ | if(_n==1)return g[0]=quick_pow(f[0],Mod-2),void(); | ||
+ | Inv(f,g,(_n+1)>>1); | ||
+ | int n=build((_n-1)<<1); | ||
+ | _for(i,(_n+1)>>1,n)g[i]=0; | ||
+ | _for(i,0,_n)temp[i]=f[i];_for(i,_n,n)temp[i]=0; | ||
+ | NTT(g,n,true);NTT(temp,n,true); | ||
+ | _for(i,0,n)g[i]=(2-1LL*temp[i]*g[i]%Mod)*g[i]%Mod; | ||
+ | NTT(g,n,false); | ||
+ | _for(i,_n,n)g[i]=0; | ||
+ | } | ||
+ | void Div(const int *f,int _n,const int *g,int _m,int *r){ | ||
+ | static int temp1[MAXN<<2],temp2[MAXN<<2]; | ||
+ | if(_n<_m){ | ||
+ | _rep(i,0,_n)r[i]=f[i]; | ||
+ | return; | ||
+ | } | ||
+ | _rep(i,0,_m)temp1[i]=g[_m-i]; | ||
+ | Inv(temp1,temp2,_n-_m+1); | ||
+ | _rep(i,0,_n)temp1[i]=f[_n-i]; | ||
+ | Mul(temp2,_n-_m+1,temp1,_n+1,_n-_m+1); | ||
+ | for(int i=0,j=_n-_m;i<j;i++,j--)swap(temp2[i],temp2[j]); | ||
+ | int __m=min(_n-_m+1,_m); | ||
+ | _for(i,0,_m)temp1[i]=g[i]; | ||
+ | Mul(temp1,_m,temp2,__m); | ||
+ | _for(i,0,_m)r[i]=(f[i]+Mod-temp1[i])%Mod; | ||
+ | } | ||
+ | } | ||
+ | int a[MAXN],b[MAXN],c[MAXN],pool[MAXN*40],*pos=pool,*g[MAXN<<2]; | ||
+ | void build(int k,int L,int R){ | ||
+ | g[k]=pos,pos+=R-L+2; | ||
+ | int M=L+R>>1; | ||
+ | if(L==R)return g[k][0]=Mod-a[M],g[k][1]=1,void(); | ||
+ | build(k<<1,L,M);build(k<<1|1,M+1,R); | ||
+ | Poly::Mul2(g[k<<1],M-L+2,g[k<<1|1],R-M+1,g[k]); | ||
+ | } | ||
+ | void query(int k,int L,int R,int *f){ | ||
+ | if(R-L<MINL){ | ||
+ | _rep(i,L,R){ | ||
+ | b[i]=0; | ||
+ | for(int j=R-L;~j;j--) | ||
+ | b[i]=(1LL*b[i]*a[i]+f[j])%Mod; | ||
+ | } | ||
+ | return; | ||
+ | } | ||
+ | int *temp=pos,M=L+R>>1;pos+=R-L+1; | ||
+ | Poly::Div(f,R-L,g[k<<1],M-L+1,temp); | ||
+ | query(k<<1,L,M,temp); | ||
+ | Poly::Div(f,R-L,g[k<<1|1],R-M,temp); | ||
+ | query(k<<1|1,M+1,R,temp); | ||
+ | } | ||
+ | int main() | ||
+ | { | ||
+ | Poly::init(); | ||
+ | int n=read_int(),m=read_int(); | ||
+ | _rep(i,0,n)c[i]=read_int(); | ||
+ | _for(i,0,m)a[i]=read_int(); | ||
+ | build(1,0,m-1); | ||
+ | int *f=pos;pos+=m; | ||
+ | Poly::Div(c,n,g[1],m,f); | ||
+ | query(1,0,m-1,f); | ||
+ | _for(i,0,m)enter(b[i]); | ||
+ | return 0; | ||
+ | } | ||
+ | </code> | ||
+ | </hidden> | ||
+ | |||
+ | ===== 多项式快速插值 ===== | ||
+ | |||
+ | ==== 算法简介 ==== | ||
+ | |||
+ | 给定 $n$ 个点 $(x_i,y_i)$,求次数不超过 $n-1$ 次的多项式 $f(x)$ 满足 $f(x_i)=y_i$。时间复杂度 $O(n\log^2 n)$,空间复杂度 $O(m\log m)$。 | ||
+ | |||
+ | ==== 算法实现 ==== | ||
+ | |||
+ | [[https://www.luogu.com.cn/problem/P5158|洛谷p5158]] | ||
+ | |||
+ | 根据拉格朗日插值法,有 | ||
+ | |||
+ | $$f(x)=\sum_{i=1}^n y_i\prod_{j=1,j\neq i}^n \frac {x-x_j}{x_i-x_j}=\sum_{i=1}^n \cfrac {y_i}{\prod_{j=1,j\neq i}(x_i-x_j)}\prod_{j=1,j\neq i}^n x-x_j$$ | ||
+ | |||
+ | 设 $g(x)=\prod_{i=1}^n x-x_i$,根据洛必达法则,有 | ||
+ | |||
+ | $$\prod_{j=1,j\neq i}^n x_i-x_j=\lim_{x\to x_i}\frac {g(x)}{x-x_i}=g'(x_i)$$ | ||
+ | |||
+ | 于是有 $f(x)=\sum_{i=1}^n \cfrac {y_i}{g'(x_i)}\prod_{j=1,j\neq i}^n x-x_j$。 | ||
+ | |||
+ | 考虑 $O(n\log^2 n)$ 分治乘法计算出 $g(x)$,再通过 $O(n\log^2 n)$ 多项式多点求值计算出所有 $g'(x_i)$。 | ||
+ | |||
+ | 设 $f_{l,r}(x)=\sum_{i=l}^r \cfrac {y_i}{g'(x_i)}\prod_{j=l,j\neq i}^r x-x_j$。考虑分治,有 | ||
+ | |||
+ | $$ | ||
+ | \begin{equation}\begin{split} | ||
+ | f_{l,r}(x)&=\left(\sum_{i=l}^m \cfrac {y_i}{g'(x_i)}\prod_{j=l,j\neq i}^m x-x_j\right)\left(\prod_{j=m+1}^r x-x_j\right)+\left(\sum_{i=m+1}^r \cfrac {y_i}{g'(x_i)}\prod_{j=m+1,j\neq i}^r x-x_j\right)\left(\prod_{j=l}^m x-x_j\right)\\ | ||
+ | &=f_{l,m}(x)\left(\prod_{j=m+1}^r x-x_j\right)+f_{m+1,r}(x)\left(\prod_{j=l}^m x-x_j\right) | ||
+ | \end{split}\end{equation} | ||
+ | $$ | ||
+ | 于是可以 $O(n\log^2 n)$ 完成分治,总时间复杂度 $O(n\log^2 n)$。 | ||
+ | <hidden 查看代码> | ||
+ | <code cpp> | ||
+ | const int MAXN=1e5+5,MINL=640,Mod=998244353; | ||
+ | int quick_pow(int a,int b){ | ||
+ | int ans=1; | ||
+ | while(b){ | ||
+ | if(b&1) | ||
+ | ans=1LL*ans*a%Mod; | ||
+ | a=1LL*a*a%Mod; | ||
+ | b>>=1; | ||
+ | } | ||
+ | return ans; | ||
+ | } | ||
+ | namespace Poly{ | ||
+ | const int G=3; | ||
+ | int rev[MAXN<<2],Pool[MAXN<<3],*Wn[30]; | ||
+ | void init(){ | ||
+ | int lg2=0,*pos=Pool,n,w; | ||
+ | while((1<<lg2)<MAXN*2)lg2++; | ||
+ | n=1<<lg2,w=quick_pow(G,(Mod-1)/(1<<lg2)); | ||
+ | while(~lg2){ | ||
+ | Wn[lg2]=pos,pos+=n; | ||
+ | Wn[lg2][0]=1; | ||
+ | _for(i,1,n)Wn[lg2][i]=1LL*Wn[lg2][i-1]*w%Mod; | ||
+ | w=1LL*w*w%Mod; | ||
+ | lg2--;n>>=1; | ||
+ | } | ||
+ | } | ||
+ | int build(int k){ | ||
+ | int n,pos=0; | ||
+ | while((1<<pos)<=k)pos++; | ||
+ | n=1<<pos; | ||
+ | _for(i,0,n)rev[i]=(rev[i>>1]>>1)|((i&1)<<(pos-1)); | ||
+ | return n; | ||
+ | } | ||
+ | void NTT(int *f,int n,bool type){ | ||
+ | _for(i,0,n)if(i<rev[i]) | ||
+ | swap(f[i],f[rev[i]]); | ||
+ | int t1,t2; | ||
+ | for(int i=1,lg2=1;i<n;i<<=1,lg2++){ | ||
+ | for(int j=0;j<n;j+=(i<<1)){ | ||
+ | _for(k,j,j+i){ | ||
+ | t1=f[k],t2=1LL*Wn[lg2][k-j]*f[k+i]%Mod; | ||
+ | f[k]=(t1+t2)%Mod,f[k+i]=(t1-t2)%Mod; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | if(!type){ | ||
+ | reverse(f+1,f+n); | ||
+ | int div=quick_pow(n,Mod-2); | ||
+ | _for(i,0,n)f[i]=(1LL*f[i]*div%Mod+Mod)%Mod; | ||
+ | } | ||
+ | } | ||
+ | void Mul(int *f,int _n,int *g,int _m,int xmod=0){ | ||
+ | int n=build(_n+_m-2); | ||
+ | _for(i,_n,n)f[i]=0;_for(i,_m,n)g[i]=0; | ||
+ | NTT(f,n,true);NTT(g,n,true); | ||
+ | _for(i,0,n)f[i]=1LL*f[i]*g[i]%Mod; | ||
+ | NTT(f,n,false); | ||
+ | if(xmod)_for(i,xmod,n)f[i]=0; | ||
+ | } | ||
+ | void Mul2(const int *f,int _n,const int *g,int _m,int *h){ | ||
+ | static int temp1[MAXN<<2],temp2[MAXN<<2]; | ||
+ | int n=build(_n+_m-2); | ||
+ | memcpy(temp1,f,sizeof(int)*_n);memcpy(temp2,g,sizeof(int)*_m); | ||
+ | _for(i,_n,n)temp1[i]=0;_for(i,_m,n)temp2[i]=0; | ||
+ | NTT(temp1,n,true);NTT(temp2,n,true); | ||
+ | _for(i,0,n)temp1[i]=1LL*temp1[i]*temp2[i]%Mod; | ||
+ | NTT(temp1,n,false); | ||
+ | n=_n+_m-1; | ||
+ | _for(i,0,n)h[i]=temp1[i]; | ||
+ | } | ||
+ | void Mul3(int *f,int _n,int *g,int _m){ | ||
+ | static int temp1[MAXN<<2],temp2[MAXN<<2]; | ||
+ | int n=build(_n+_m-2); | ||
+ | memcpy(temp1,f,sizeof(int)*_n);memcpy(temp2,g,sizeof(int)*_m); | ||
+ | _for(i,_n,n)temp1[i]=0;_for(i,_m,n)temp2[i]=0; | ||
+ | NTT(temp1,n,true);NTT(temp2,n,true); | ||
+ | _for(i,0,n)temp1[i]=1LL*temp1[i]*temp2[i]%Mod; | ||
+ | NTT(temp1,n,false); | ||
+ | n=_n+_m-1; | ||
+ | _for(i,0,n)f[i]=temp1[i]; | ||
+ | } | ||
+ | void Inv(const int *f,int *g,int _n){ | ||
+ | static int temp[MAXN<<2]; | ||
+ | if(_n==1)return g[0]=quick_pow(f[0],Mod-2),void(); | ||
+ | Inv(f,g,(_n+1)>>1); | ||
+ | int n=build((_n-1)<<1); | ||
+ | _for(i,(_n+1)>>1,n)g[i]=0; | ||
+ | _for(i,0,_n)temp[i]=f[i];_for(i,_n,n)temp[i]=0; | ||
+ | NTT(g,n,true);NTT(temp,n,true); | ||
+ | _for(i,0,n)g[i]=(2-1LL*temp[i]*g[i]%Mod)*g[i]%Mod; | ||
+ | NTT(g,n,false); | ||
+ | _for(i,_n,n)g[i]=0; | ||
+ | } | ||
+ | void Div(const int *f,int _n,const int *g,int _m,int *r){ | ||
+ | static int temp1[MAXN<<2],temp2[MAXN<<2]; | ||
+ | if(_n<_m){ | ||
+ | _rep(i,0,_n)r[i]=f[i]; | ||
+ | return; | ||
+ | } | ||
+ | _rep(i,0,_m)temp1[i]=g[_m-i]; | ||
+ | Inv(temp1,temp2,_n-_m+1); | ||
+ | _rep(i,0,_n)temp1[i]=f[_n-i]; | ||
+ | Mul(temp2,_n-_m+1,temp1,_n+1,_n-_m+1); | ||
+ | for(int i=0,j=_n-_m;i<j;i++,j--)swap(temp2[i],temp2[j]); | ||
+ | int __m=min(_n-_m+1,_m); | ||
+ | _for(i,0,_m)temp1[i]=g[i]; | ||
+ | Mul(temp1,_m,temp2,__m); | ||
+ | _for(i,0,_m)r[i]=(f[i]+Mod-temp1[i])%Mod; | ||
+ | } | ||
+ | } | ||
+ | int x[MAXN],y[MAXN],gy[MAXN],pool[MAXN*50],*pos=pool,*t[MAXN<<2]; | ||
+ | void build(int k,int L,int R){ | ||
+ | t[k]=pos,pos+=R-L+2; | ||
+ | int M=L+R>>1; | ||
+ | if(L==R)return t[k][0]=Mod-x[M],t[k][1]=1,void(); | ||
+ | build(k<<1,L,M);build(k<<1|1,M+1,R); | ||
+ | Poly::Mul2(t[k<<1],M-L+2,t[k<<1|1],R-M+1,t[k]); | ||
+ | } | ||
+ | void query(int k,int L,int R,int *f){ | ||
+ | if(R-L<MINL){ | ||
+ | _rep(i,L,R){ | ||
+ | gy[i]=0; | ||
+ | for(int j=R-L;~j;j--) | ||
+ | gy[i]=(1LL*gy[i]*x[i]+f[j])%Mod; | ||
+ | } | ||
+ | return; | ||
+ | } | ||
+ | int *temp=pos,M=L+R>>1;pos+=R-L+1; | ||
+ | Poly::Div(f,R-L,t[k<<1],M-L+1,temp); | ||
+ | query(k<<1,L,M,temp); | ||
+ | Poly::Div(f,R-L,t[k<<1|1],R-M,temp); | ||
+ | query(k<<1|1,M+1,R,temp); | ||
+ | } | ||
+ | void query2(int k,int L,int R,int *f){ | ||
+ | int M=L+R>>1; | ||
+ | if(L==R)return f[0]=1LL*y[M]*quick_pow(gy[M],Mod-2)%Mod,void(); | ||
+ | int *temp=pos;pos+=R-L+1; | ||
+ | query2(k<<1,L,M,f);query2(k<<1|1,M+1,R,temp); | ||
+ | Poly::Mul3(f,M-L+1,t[k<<1|1],R-M+1);Poly::Mul3(temp,R-M,t[k<<1],M-L+2); | ||
+ | int n=R-L+1; | ||
+ | _for(i,0,n)f[i]=(f[i]+temp[i])%Mod; | ||
+ | } | ||
+ | int main() | ||
+ | { | ||
+ | Poly::init(); | ||
+ | int n=read_int(); | ||
+ | _for(i,0,n)x[i]=read_int(),y[i]=read_int(); | ||
+ | build(1,0,n-1); | ||
+ | int *g=pos;pos+=n; | ||
+ | memcpy(g,t[1],sizeof(int)*(n+1)); | ||
+ | _for(i,0,n)g[i]=1LL*g[i+1]*(i+1)%Mod; | ||
+ | query(1,0,n-1,g); | ||
+ | int *f=pos;pos+=n; | ||
+ | query2(1,0,n-1,f); | ||
+ | _for(i,0,n)space(f[i]); | ||
+ | return 0; | ||
+ | } | ||
+ | </code> | ||
+ | </hidden> |