这个大家应该都会吧
不推了。
直接上代码:
friend inline poly operator+(const poly&a,const poly&b){
poly ret(max(a.deg(),b.deg()));
for(ri i=0;i<=a.deg();++i)ret[i]=add(ret[i],a[i]);
for(ri i=0;i<=b.deg();++i)ret[i]=add(ret[i],b[i]);
return ret;
}
friend inline poly operator-(const poly&a,const poly&b){
poly ret(max(a.deg(),b.deg()));
for(ri i=0;i<=a.deg();++i)ret[i]=add(ret[i],a[i]);
for(ri i=0;i<=b.deg();++i)ret[i]=dec(ret[i],b[i]);
return ret;
}
friend inline poly operator*(const int&a,const poly&b){
poly ret(b.deg());
for(ri i=0;i<=b.deg();++i)ret[i]=mul(a,b[i]);
return ret;
}
这个大家应该都会吧
还是推一推吧。
已知的:
A ( x ) = ∑ i = 0 n a i x i A(x)=\sum_{i=0}^na_ix^i A(x)=∑i=0naixi
B ( x ) = ∑ i = 0 m b i x i B(x)=\sum_{i=0}^mb_ix^i B(x)=∑i=0mbixi
要求的:
C ( x ) = A ( x ) B ( x ) = ∑ i = 0 n + m ( ∑ j = 0 m i n { i , n } a j ∗ b i − j ) x i C(x)=A(x)B(x)=\sum_{i=0}^{n+m}(\sum_{j=0}^{min\{i,n\}}a_j*b_{i-j})x^i C(x)=A(x)B(x)=∑i=0n+m(∑j=0min{i,n}aj∗bi−j)xi
显然直接暴力做是 O ( n 2 ) O(n^2) O(n2)的,考虑如何优化。
那么我们使用 f f t fft fft或者 n t t ntt ntt来实现点值表示法和系数表示法之间的快速转化。
为了方便起见,我们将 A , B A,B A,B的最高次数统一成一个 2 2 2的幂(对于超过 n / m n/m n/m的项的系数看成0即可)
所谓的系数表示法就是我们平常用的那种。
而点值表示法,就是把这个多项式理解成一个函数,用这个函数上的若干个点的坐标来描述这个多项式:
f ( x ) = ( x 0 , y 0 ) , ( x 1 , y 1 ) , . . . , ( x n , y n ) = p 0 , p 1 , p 2 , . . . , p n f(x)=(x_0,y_0),(x_1,y_1),...,(x_n,y_n)=p_0,p_1,p_2,...,p_n f(x)=(x0,y0),(x1,y1),...,(xn,yn)=p0,p1,p2,...,pn
假设我们已经将 A , B A,B A,B两个函数转化成了点值表示,于是就可以马上求出 C C C的点值表示:
A ( x ) = ( x a , 0 , y a , 0 ) , ( x a , 1 , y a , 1 ) , . . . , ( x a , n − 1 , y a , n − 1 ) = p a , 0 , p a , 1 , . . . , p a , n − 1 A(x)=(x_{a,0},y_{a,0}),(x_{a,1},y_{a,1}),...,(x_{a,n-1},y_{a,n-1})=p_{a,0},p_{a,1},...,p_{a,n-1} A(x)=(xa,0,ya,0),(xa,1,ya,1),...,(xa,n−1,ya,n−1)=pa,0,pa,1,...,pa,n−1
B ( x ) = ( x b , 0 , y b , 0 ) , ( x b , 1 , y b , 1 ) , . . . , ( x b , n − 1 , y b , n − 1 ) = p b , 0 , p b , 1 , . . . , p b , n − 1 B(x)=(x_{b,0},y_{b,0}),(x_{b,1},y_{b,1}),...,(x_{b,n-1},y_{b,n-1})=p_{b,0},p_{b,1},...,p_{b,n-1} B(x)=(xb,0,yb,0),(xb,1,yb,1),...,(xb,n−1,yb,n−1)=pb,0,pb,1,...,pb,n−1
那么 C ( x ) = p a , 0 ∗ p b , 0 , p a , 1 ∗ p b , 1 , . . . , p a , n − 1 ∗ p b , n − 1 = p c , 0 , p c , 1 , . . . , p c , n − 1 C(x)=p_{a,0}*p_{b,0}, p_{a,1}*p_{b,1},...,p_{a,n-1}*p_{b,n-1}=p_{c,0},p_{c,1},...,p_{c,n-1} C(x)=pa,0∗pb,0,pa,1∗pb,1,...,pa,n−1∗pb,n−1=pc,0,pc,1,...,pc,n−1
然后再把 C ( x ) C(x) C(x)还原成系数表达式即可。
注意:我们需要保证 x a i , x b i x_{ai},x_{bi} xai,xbi互不相同)
现在就只用考虑如何实现点值表示和系数表示的互换了。
也就是如何用更少的计算次数来求出 n n n个不同的 x x x值对应的 y y y值。
考虑有两个具有特殊性质的东西:
单位根保证了 w n 0 , w n 1 , . . . , w n n − 1 w_n^0,w_n^1,...,w_n^{n-1} wn0,wn1,...,wnn−1是互不相同的并且有 w n i j = ( w n i ) j w_n^{ij}=(w_n^i)^j wnij=(wni)j
而原根在模数为质数 p p p的时候也有 g 0 , g 1 , g 2 , . . . , g n − 1 g^0,g^1,g^2,...,g^{n-1} g0,g1,g2,...,gn−1是互不相同的并且 g i j ≡ ( g i ) j m o d    p g^{ij}\equiv (g^i)^j \mod p gij≡(gi)jmodp
这满足了我们上面的性质,因此我们考虑将 w n 0 , w n 1 , . . . w n n − 1 w_n^0,w_n^1,...w_n^{n-1} wn0,wn1,...wnn−1作为 x 0 , x 1 , . . . x n − 1 x_0,x_1,...x_{n-1} x0,x1,...xn−1带入求点值。
然后要用到两个引理:
这两个引理可以画个单位圆简单证明(建议各位神犇自己简要证明一下)
然后利用按照下标的奇偶性来进行分治处理:
f ( x ) = ∑ i = 0 n a i x i f(x)=\sum_{i=0}^na_ix^i f(x)=∑i=0naixi
=> f ( w n k ) = ∑ i = 0 n a i w n i k f(w_n^k)=\sum_{i=0}^na_i w_n^{ik} f(wnk)=∑i=0naiwnik
=> f ( w n k ) = ∑ i = 0 n 2 − 1 a 2 i w n 2 i k + w n k ∑ i = 0 n 2 − 1 a 2 i + 1 w n 2 i k f(w_n^k)=\sum_{i=0}^{\frac n2-1}a_{2i}w_n^{2ik}+w_n^k\sum_{i=0}^{\frac n2-1}a_{2i+1}w_n^{2ik} f(wnk)=∑i=02n−1a2iwn2ik+wnk∑i=02n−1a2i+1wn2ik
同时又有:
f ( w n k + n 2 ) = ∑ i = 0 n 2 − 1 a 2 i w n 2 i k − w n k ∑ i = 0 n 2 − 1 a 2 i + 1 w n 2 i k f(w_n^{k+\frac n2})=\sum_{i=0}^{\frac n2-1}a_{2i}w_n^{2ik}-w_n^k\sum_{i=0}^{\frac n2-1}a_{2i+1}w_n^{2ik} f(wnk+2n)=∑i=02n−1a2iwn2ik−wnk∑i=02n−1a2i+1wn2ik
这一步需要用到引理
所以我们只要算出两个重新分配了系数的多项式的值就可以了。
显然一直分下去只有 l o g log log层。
于是总时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
注意到递归的效率很低,我们可以预处理最后一层的系数的位置然后用迭代的方式还原回去来优化常数
代码:
vector<int>pos;
int lim,tim;
inline void init(int up){
lim=1,tim=0;
while(lim<=up)lim<<=1,++tim;
pos.resize(lim),pos[0]=0;
for(ri i=0;i<lim;++i)pos[i]=(pos[i>>1]>>1)|((i&1)<<(tim-1));
}
原理:
我们发现分治完之后相当于将 i i i和 i i i对应的二进制数在 l i m lim lim下反转之后对应的新二进制数 i ′ i' i′这两个位置的系数 a i , a i ′ a_i,a_i' ai,ai′交换了位置。
这就成功的实现了系数转点值。
下面来看点值转系数:
注:下面的图片有几点没写清楚:
以上是 f f t fft fft的证明, n t t ntt ntt同理。
代码:
fft:
//复数定义
struct Complex{
double x,y;
friend inline Complex operator+(const Complex&a,const Complex&b){return (Complex){a.x+b.x,a.y+b.y};}
friend inline Complex operator-(const Complex&a,const Complex&b){return (Complex){a.x-b.x,a.y-b.y};}
friend inline Complex operator*(const Complex&a,const Complex&b){return (Complex){a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x};}
}a[N],b[N];
//fft
inline void fft(Complex a[],int type){
for(int i=0;i<lim;++i)if(i<pos[i])swap(a[i],a[pos[i]]);
for(int mid=1;mid<lim;mid<<=1){
Complex w_n=(Complex){cos(pi/mid),type*sin(pi/mid)};
for(int j=0,len=mid<<1;j<lim;j+=len){
Complex w=(Complex){1,0};
for(int k=0;k<mid;++k,w=w*w_n){
Complex a0=a[j+k],a1=w*a[j+k+mid];
a[j+k]=a0+a1,a[j+k+mid]=a0-a1;
}
}
}
}
ntt:
inline void ntt(vector<int>&a,int type){
for(ri i=0;i<lim;++i)if(i<pos[i])swap(a[i],a[pos[i]]);
for(ri mid=1,wn,mult=(mod-1)/2,typ=type==1?3:(mod+1)/3;mid<lim;mid<<=1,mult>>=1){
wn=ksm(typ,mult);
for(ri j=0,len=mid<<1;j<lim;j+=len)for(ri w=1,a0,a1,k=0;k<mid;++k,w=mul(w,wn)){
a0=a[j+k],a1=mul(w,a[j+k+mid]);
a[j+k]=add(a0,a1),a[j+k+mid]=dec(a0,a1);
}
}
if(type==-1)for(ri i=0,inv=ksm(lim,mod-2);i<lim;++i)a[i]=mul(a[i],inv);
}
多项式相乘:
friend inline poly operator*(const poly&a,const poly&b){
int n=a.deg(),m=b.deg();
init(n+m),A.resize(lim),B.resize(lim);
poly ret(lim-1);
for(ri i=0;i<=n;++i)A[i]=a[i];
for(ri i=0;i<=m;++i)B[i]=b[i];
for(ri i=n+1;i<lim;++i)A[i]=0;
for(ri i=m+1;i<lim;++i)B[i]=0;
ntt(A,1),ntt(B,1);
for(ri i=0;i<lim;++i)A[i]=mul(A[i],B[i]);
return ntt(A,-1),ret.a=A,ret;
}
一道板题:uoj#34多项式乘法
f f t fft fft写法
n t t ntt ntt写法
乘法可以用 f f t / n t t fft/ntt fft/ntt加速,因为每次递归的时候多项式最高次项都减少一半,所以总复杂度仍然是 O ( n l o g n ) O(nlogn) O(nlogn)
代码:
vector<int>A,B;
inline poly poly_inv(poly a,const int&k){
a=a.extend(k);
if(k==1)return poly(0,ksm(a[0],mod-2));
poly f0=poly_inv(a,(k+1)>>1);
return (2*f0-((f0*f0.extend(k))*a).extend(k)).extend(k);
}
一道板题:洛谷4238
多项式求逆板
默认大家都会函数求导
这个多项式求导属于最简单的那一种
对于一个多项式 f ( x ) = ∑ i = 0 n a i x i f(x)=\sum_{i=0}^na_ix^i f(x)=∑i=0naixi
它求导的结果 f ′ ( x ) = ∑ i = 0 n − 1 ( i + 1 ) a i + 1 x i f'(x)=\sum_{i=0}^{n-1}(i+1)a_{i+1}x^i f′(x)=∑i=0n−1(i+1)ai+1xi
于是直接模拟即可。
代码:
inline poly poly_direv(poly a){
poly ret(a.deg()-1);
for(ri i=0;i<=ret.deg();++i)ret[i]=mul(a[i+1],i+1);
return ret;
}
相当于是多项式求导的逆运算。
对于一个多项式 f ( x ) = ∑ i = 0 n a i x i f(x)=\sum_{i=0}^na_ix^i f(x)=∑i=0naixi
它求导的结果 ∫ f ( x ) d x = ∑ i = 1 n + 1 a i − 1 i x i \int\mathrm f(x)dx=\sum_{i=1}^{n+1}\frac{a_{i-1}}ix^i ∫f(x)dx=∑i=1n+1iai−1xi
可以看出来一个函数的导函数积分起来等价于自己。
代码:
inline poly poly_inter(poly a){
poly ret(a.deg()+1);
for(ri i=1;i<=ret.deg();++i)ret[i]=mul(Inv[i],a[i-1]);
return ret;
}
我们令 g ( x ) = l n f ( x ) g(x)=lnf(x) g(x)=lnf(x)
那么根据链式法则求导知:
g ′ ( x ) = f ′ ( x ) f ( x ) g'(x)=\frac{f'(x)}{f(x)} g′(x)=f(x)f′(x)
我们已经会多项式求逆和多项式积分,多项式求导了,于是就成功解决了多项式取对。
代码:
inline poly poly_ln(poly a,int len){
poly ret=a.poly_direv(a);
return ret=ret*a.poly_inv(a,len),ret.poly_inter(ret);
}
这个时候我们要提到一个重要的方法:牛顿迭代法
假设我们要求 h ( B ( x ) ) ≡ A ( x ) m o d    x n h(B(x))\equiv A(x) \mod x^n h(B(x))≡A(x)modxn中的 B ( x ) B(x) B(x)
现在考虑构造有一个以多项式为变量的函数 g ( f ) = h ( f ) − A g(f)=h(f)-A g(f)=h(f)−A
那么要求的就是 g ( f ) g(f) g(f)模 x n x^n xn意义下的零点。
假设已经求出了 g ( f ) g(f) g(f)模 x ⌊ x 2 ⌋ x^{\left\lfloor\frac x 2\right\rfloor} x⌊2x⌋的零点 f 0 f_0 f0
那么现在 g ( f a ) = g ( f 0 ) + g ′ ( f 0 ) ( f a − f 0 ) + g ′ ′ ( f 0 ) 2 ( f a − f 0 ) 2 + . . . g(f_a)=g(f_0)+g'(f_0)(f_a-f_0)+\frac{g''(f_0)}2(f_a-f_0)^2+... g(fa)=g(f0)+g′(f0)(fa−f0)+2g′′(f0)(fa−f0)2+... 泰勒展开
由于 f 0 f_0 f0是模 x ⌊ x 2 ⌋ x^{\left\lfloor\frac x 2\right\rfloor} x⌊2x⌋的零点,所以有:
g ( f a ) ≡ g ( f 0 ) + g ′ ( f 0 ) ( f a − f 0 ) ≡ 0 m o d    x n g(f_a)\equiv g(f_0)+g'(f_0)(f_a-f_0)\equiv0\mod x^{n} g(fa)≡g(f0)+g′(f0)(fa−f0)≡0modxn
所以移项后发现 f a = f 0 − g ( f 0 ) g ′ ( f 0 ) f_a=f_0-\frac{g(f_0)}{g'(f_0)} fa=f0−g′(f0)g(f0)
这个东西有什么用呢?
我们简单举个例子:
比如说多项式求逆,可以构造 g ( f ) = 1 f − A g(f)=\frac1f-A g(f)=f1−A,算出来 f a = 2 f 0 − A f 0 2 f_a=2f_0-Af_0^2 fa=2f0−Af02省去了之间的麻烦。
再比如说现在要求的多项式取 e x p exp exp:
构造 g ( f ) = e f − A g(f)=e^f-A g(f)=ef−A,算出来 f a = f 0 ( 1 − l n f 0 + A ) f_a=f_0(1-lnf_0+A) fa=f0(1−lnf0+A)
然后就做完了。
代码:
inline poly poly_exp(poly a,const int&k){
a=a.extend(k);
if(k==1)return poly(0,1);
poly f0=poly_exp(a,(k+1)>>1).extend(k);
poly delt=a-poly_ln(f0,k);
++delt[0];
return (f0*delt).extend(k);
}
直接使用上面所说的牛顿迭代的结论,令 g ( x ) = f 2 − A g(x)=f^2-A g(x)=f2−A,带入得到: f a = f 0 2 + A 2 f 0 f_a=\frac{f_0^2+A}{2f_0} fa=2f0f02+A
然后用多项式求逆搞一搞即可。
代码:
inline poly poly_sqrt(poly a,const int&k){
a=a.extend(k);
if(k==1)return poly(0,1);
poly f0=poly_sqrt(a,(k+1)>>1).extend(k);
return (((f0*f0).extend(k)+a)*poly_inv((2*f0),k)).extend(k);
}
现在有两个多项式:
A ( x ) = ∑ i = 0 n a i x i , B ( x ) = ∑ i = 0 m b i x i , n > m A(x)=\sum_{i=0}^na_ix^i,B(x)=\sum_{i=0}^mb_ix^i,n>m A(x)=∑i=0naixi,B(x)=∑i=0mbixi,n>m
要求出 C ( x ) = ∑ i = 0 n − m c i x i , D ( x ) = ∑ i = 0 t d i x i , d < m C(x)=\sum_{i=0}^{n-m}c_ix^i,D(x)=\sum_{i=0}^td_ix^i,d<m C(x)=∑i=0n−mcixi,D(x)=∑i=0tdixi,d<m,满足 A ( x ) = B ( x ) C ( x ) + D ( x ) A(x)=B(x)C(x)+D(x) A(x)=B(x)C(x)+D(x),其中 C ( x ) C(x) C(x)类比商, D ( x ) D(x) D(x)类比余数。
感觉想法比较神奇。
对于一个多项式 f ( x ) f(x) f(x),我们定义一个 f R ( x ) f_R(x) fR(x)表示将这个多项式的系数翻转之后得到的新多项式,如 f ( x ) = 2 x 3 + 3 x 2 + x + 5 f(x)=2x^3+3x^2+x+5 f(x)=2x3+3x2+x+5时, f R ( x ) = 5 x 3 + x 2 + 3 x + 2 f_R(x)=5x^3+x^2+3x+2 fR(x)=5x3+x2+3x+2
然后可以惊奇的发现: f R ( x ) = x n f ( 1 x ) f_R(x)=x^nf(\frac1x) fR(x)=xnf(x1)
然后就有 A ( 1 x ) = B ( 1 x ) C ( 1 x ) + D ( 1 x ) A(\frac 1x)=B(\frac 1x)C(\frac 1x)+D(\frac 1x) A(x1)=B(x1)C(x1)+D(x1)
所以 x n A ( 1 x ) = x n B ( 1 x ) C ( 1 x ) + x m D ( 1 x ) x^nA(\frac 1x)=x^nB(\frac 1x)C(\frac 1x)+x^mD(\frac 1x) xnA(x1)=xnB(x1)C(x1)+xmD(x1)
所以 A R ( x ) = B R ( x ) C R ( x ) + D R ( x ) ∗ x n − m + 1 A_R(x)=B_R(x)C_R(x)+D_R(x)*x^{n-m+1} AR(x)=BR(x)CR(x)+DR(x)∗xn−m+1
因此 A R ( x ) ≡ B R ( x ) C R ( x ) m o d    x n − m + 1 A_R(x)\equiv B_R(x)C_R(x)\mod x^{n-m+1} AR(x)≡BR(x)CR(x)modxn−m+1
所以 C R ( x ) ≡ A R ( x ) ( B R ( x ) ) − 1 m o d    x n − m + 1 C_R(x)\equiv A_R(x) (B_R(x))^{-1} \mod x^{n-m+1} CR(x)≡AR(x)(BR(x))−1modxn−m+1
D ( x ) = A ( x ) − B ( x ) C ( x ) D(x)=A(x)-B(x)C(x) D(x)=A(x)−B(x)C(x)
于是我们靠 n t t ntt ntt和多项式求逆解决了这一问题。
代码:
friend inline poly operator/(const poly&a,const poly&b){
poly ta=a,tb=b;
int len=1,up=a.deg()-b.deg();
reverse(ta.a.begin(),ta.a.end()),reverse(tb.a.begin(),tb.a.end());
ta.extend(up),tb.extend(up);
while(len<=up)len<<=1;
tb=tb.poly_inv(tb,len).extend(up);
return ta=(ta*tb).extend(up),reverse(ta.a.begin(),ta.a.end()),ta;
}
friend inline poly operator%(const poly&a,const poly&b){return a-a/b;}
总的代码(缺三角函数以后更新):
#include
#define ri register int
using namespace std;
inline int read(){
int ans=0;
char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
return ans;
}
typedef long long ll;
const int mod=998244353;
int n,lim,tim,m;
vector<int>A,B,pos,Inv;
#define add(a,b) ((a)+(b)>=mod?(a)+(b)-mod:(a)+(b))
#define dec(a,b) ((a)>=(b)?(a)-(b):(a)-(b)+mod)
#define mul(a,b) ((ll)(a)*(b)%mod)
inline int ksm(int a,int p){int ret=1;for(;p;p>>=1,a=mul(a,a))if(p&1)ret=mul(ret,a);return ret;}
inline void ntt(vector<int>&a,const int&type){
for(ri i=0;i<lim;++i)if(i<pos[i])swap(a[i],a[pos[i]]);
for(ri mid=1,wn,mult=(mod-1)/2,typ=type==1?3:(mod+1)/3;mid<lim;mid<<=1,mult>>=1){
wn=ksm(typ,mult);
for(ri j=0,len=mid<<1;j<lim;j+=len)for(ri w=1,a0,a1,k=0;k<mid;++k,w=mul(w,wn)){
a0=a[j+k],a1=mul(w,a[j+k+mid]);
a[j+k]=add(a0,a1),a[j+k+mid]=dec(a0,a1);
}
}
if(type==-1)for(ri i=0,inv=ksm(lim,mod-2);i<lim;++i)a[i]=mul(a[i],inv);
}
inline void init(const int&up){
lim=1,tim=0;
while(lim<=up)lim<<=1,++tim;
pos.resize(lim),pos[0]=0;
for(ri i=0;i<lim;++i)pos[i]=(pos[i>>1]>>1)|((i&1)<<(tim-1));
}
struct poly{
vector<int>a;
inline int deg()const{return a.size()-1;}
poly(int k,int x=0){a.resize(k+1),a[k]=x;}
inline int&operator[](const int&k){return a[k];}
inline const int&operator[](const int&k)const{return a[k];}
inline poly extend(const int&k){poly ret=*this;return ret.a.resize(k),ret;}
friend inline poly operator+(const poly&a,const poly&b){
poly ret(max(a.deg(),b.deg()));
for(ri i=0;i<=a.deg();++i)ret[i]=add(ret[i],a[i]);
for(ri i=0;i<=b.deg();++i)ret[i]=add(ret[i],b[i]);
return ret;
}
friend inline poly operator-(const poly&a,const poly&b){
poly ret(max(a.deg(),b.deg()));
for(ri i=0;i<=a.deg();++i)ret[i]=add(ret[i],a[i]);
for(ri i=0;i<=b.deg();++i)ret[i]=dec(ret[i],b[i]);
return ret;
}
friend inline poly operator*(const int&a,const poly&b){
poly ret(b.deg());
for(ri i=0;i<=b.deg();++i)ret[i]=mul(a,b[i]);
return ret;
}
friend inline poly operator*(const poly&a,const poly&b){
int n=a.deg(),m=b.deg();
init(n+m),A.resize(lim),B.resize(lim);
poly ret(lim-1);
for(ri i=0;i<=n;++i)A[i]=a[i];
for(ri i=0;i<=m;++i)B[i]=b[i];
for(ri i=n+1;i<lim;++i)A[i]=0;
for(ri i=m+1;i<lim;++i)B[i]=0;
ntt(A,1),ntt(B,1);
for(ri i=0;i<lim;++i)A[i]=mul(A[i],B[i]);
return ntt(A,-1),ret.a=A,ret;
}
inline poly poly_inv(poly a,const int&k){
a=a.extend(k);
if(k==1)return poly(0,ksm(a[0],mod-2));
poly f0=poly_inv(a,(k+1)>>1);
return (2*f0-((f0*f0.extend(k))*a).extend(k)).extend(k);
}
inline poly poly_direv(poly a){
poly ret(a.deg()-1);
for(ri i=0;i<=ret.deg();++i)ret[i]=mul(a[i+1],i+1);
return ret;
}
inline poly poly_inter(poly a){
poly ret(a.deg()+1);
for(ri i=1;i<=ret.deg();++i)ret[i]=mul(Inv[i],a[i-1]);
return ret;
}
inline poly poly_ln(poly a,int len){
poly ret=a.poly_direv(a);
return ret=ret*a.poly_inv(a,len),ret.poly_inter(ret);
}
inline poly poly_exp(poly a,const int&k){
a=a.extend(k);
if(k==1)return poly(0,1);
poly f0=poly_exp(a,(k+1)>>1).extend(k),delt=a-poly_ln(f0,k);
++delt[0];
return (f0*delt).extend(k);
}
inline poly poly_sqrt(poly a,const int&k){
a=a.extend(k);
if(k==1)return poly(0,1);
poly f0=poly_sqrt(a,(k+1)>>1).extend(k);
return (((f0*f0).extend(k)+a)*poly_inv((2*f0),k)).extend(k);
}
friend inline poly operator/(const poly&a,const poly&b){
poly ta=a,tb=b;
int len=1,up=a.deg()-b.deg();
reverse(ta.a.begin(),ta.a.end()),reverse(tb.a.begin(),tb.a.end());
ta.extend(up),tb.extend(up);
while(len<=up)len<<=1;
tb=tb.poly_inv(tb,len).extend(up);
return ta=(ta*tb).extend(up),reverse(ta.a.begin(),ta.a.end()),ta;
}
friend inline poly operator%(const poly&a,const poly&b){return a-a/b;}
};
int main(){
...
return 0;
}
一个听起来挺高大上的东西,然而很简单。
前置知识: c d q cdq cdq分治, f f t fft fft, n t t ntt ntt
考虑这样一个转移式子 f 0 = 0 , f i = ∑ j = 1 i f i − j g j f_0=0,f_i=\sum_{j=1}^if_{i-j}g_j f0=0,fi=∑j=1ifi−jgj,其中 g g g数组已知,让你求 f f f数组。
容易观察到,这个转移式是一个卷积的形式。
然而并不能直接 f f t fft fft因为 f f f的转移跟自身有关,当然你可以使用我们马上下面讲的生成函数秒掉。
生成函数做法:
我们对 f , g f,g f,g构造生成函数 F ( x ) , G ( x ) F(x),G(x) F(x),G(x)
那么 F ( x ) − f 0 = G ( x ) F ( x ) F(x)-f_0=G(x)F(x) F(x)−f0=G(x)F(x)
所以 F ( x ) = f 0 G ( x ) − 1 F(x)=\frac{f_0}{G(x)-1} F(x)=G(x)−1f0,直接上多项式求逆即可,时间复杂度为 O ( n l o g n ) O(nlog_n) O(nlogn)吊打分治FFT。
新的做法:分治FFT
假设现在要求的 f f f值的下标为 [ l , r ] [l,r] [l,r],我们可以利用类似 c d q cdq cdq分治的思想,先递归求出 [ l , m i d ] [l,mid] [l,mid]这一段的 f f f值,然后考虑 [ l , m i d ] [l,mid] [l,mid]对 [ m i d + 1 , r ] [mid+1,r] [mid+1,r]的贡献,最后递归 [ m i d + 1 , r ] [mid+1,r] [mid+1,r]即可。
那么现在要考虑的就只有 [ l , m i d ] [l,mid] [l,mid]对 [ m i d + 1 , r ] [mid+1,r] [mid+1,r]的贡献啦!
对于 f i , i ∈ [ m i d + 1 , r ] , f j , j ∈ [ l , m i d ] , f i + = f j ∗ g i − j f_i,i\in[mid+1,r],f_j,j\in[l,mid],f_i+=f_j*g_{i-j} fi,i∈[mid+1,r],fj,j∈[l,mid],fi+=fj∗gi−j,然后把所有的放在一起考虑就成了一个卷积的形式。
于是我们将 f l , l + 1 , . . . , m i d f_{l,l+1,...,mid} fl,l+1,...,mid构成的多项式和 g 0 , 1 , . . . , r − l g_{0,1,...,r-l} g0,1,...,r−l构成的多项式乘起来更新 f m i d + 1 , m i d + 2 , . . . , r f_{mid+1,mid+2,...,r} fmid+1,mid+2,...,r即可。
然而时间复杂度为 O ( n l o g n 2 ) O(nlog_n^2) O(nlogn2)
代码:
inline void cdqFFT(poly&a,poly&b,int l,int r){
if(l==r)return;
int mid=l+r>>1;
cdqFFT(a,b,l,mid),init(2*(r-l+1));
for(ri i=0;i<lim;++i)A[i]=B[i]=0;
for(ri i=l;i<=mid;++i)A[i-l]=a[i];
for(ri i=0;i<=r-l;++i)B[i]=b[i];
ntt(A,1),ntt(B,1);
for(ri i=0;i<lim;++i)A[i]=mul(A[i],B[i]);
ntt(A,-1);
for(ri i=mid+1;i<=r;++i)a[i]=add(a[i],A[i-l]);
cdqFFT(a,b,mid+1,r);
}
我对于生成函数的理解:生成函数就相当于对一个集合的表示:
一般生成函数(Ordinary Generating Function) 也就是大家常说的 O G F OGF OGF:
F ( x ) = ∑ i = 0 ∞ a i x i F(x)=\sum_{i=0}^{\infty}a_ix^i F(x)=∑i=0∞aixi
指数生成函数(Exponential Generating Function) 也就是大家常说的 E G F EGF EGF:
F ( x ) = ∑ i = 0 ∞ a i x i i ! F(x)=\sum_{i=0}^{\infty}a_i\frac{x^i}{i!} F(x)=∑i=0∞aii!xi
它们可以帮助我们处理一些组合问题。
两个经常用到的公式:
1 + x + x 2 + . . . = 1 1 − x 1+x+x^2+...=\frac{1}{1-x} 1+x+x2+...=1−x1
1 + x + x 3 + . . . + x n = 1 − x n + 1 1 − x 1+x+x^3+...+x^n=\frac{1-x^{n+1}}{1-x} 1+x+x3+...+xn=1−x1−xn+1小学的等比数列求和公式
以及我们的泰勒展开公式。
解决组合问题的时候我们通常将 x i x^i xi的系数看成值为 i i i的数被凑出的方案数
事实上,我认为加法原理和乘法原理在生成函数上同样对应了具体的运算。
比如说现在我们要从集合 A 1 , A 2 , A 3 , . . , A n A_1,A_2,A_3,..,A_n A1,A2,A3,..,An中选一个值为 i i i的数出来问有多少种选法(加法原理),那么考虑这 n n n个生成函数的和 C C C, C C C中 x i x^i xi的系数就是答案。
再比如说我们要从 A 1 , A 2 , A 3 , . . . , A n A_1,A_2,A_3,...,A_n A1,A2,A3,...,An中各选一个数加起来,问加和为 i i i的方案数(乘法原理),那么考虑这 n n n个生成函数的积 C C C, C C C中 x i x^i xi的系数就是答案。
是不是有点感觉了?
我们举一个例子:正整数集 N = { 1 , 2 , 3 , 4 , . . . } N=\{1,2,3,4,...\} N={1,2,3,4,...},元素的大小定义为它的数值,定义 S E Q ( A ) SEQ(A) SEQ(A) 是由 A A A的元素排成的序列组成的集合,一个序列的大小定义为其元素大小总和,现在让我们求一求 S E Q ( N ) SEQ(N) SEQ(N)。
S E Q ( N ) = { 正 整 数 有 序 拆 分 } = { 0 , 1 , 1 + 1 , 2 , 1 + 1 + 1 , 1 + 2 , 2 + 1 , 3 } SEQ(N) = \{正整数有序拆分\}=\{0,1,1+1,2,1+1+1,1+2, 2+1,3\} SEQ(N)={正整数有序拆分}={0,1,1+1,2,1+1+1,1+2,2+1,3}
考虑构造一个函数 N ( x ) = x + x 2 + . . . = x 1 − x N(x)=x+x^2+...=\frac x{1-x} N(x)=x+x2+...=1−xx
于是 S E Q ( N ) = 1 + N ( x ) + N 2 ( x ) + . . . = 1 1 − N ( x ) = 1 + x 1 − 2 x = 1 + x + 2 x + 4 x 2 + . . . SEQ(N)=1+N(x)+N^2(x)+...=\frac 1{1-N(x)}=1+\frac x{1-2x}=1+x+2x+4x^2+... SEQ(N)=1+N(x)+N2(x)+...=1−N(x)1=1+1−2xx=1+x+2x+4x2+...
是不是感觉挺好用的
那么来一道板题:bzoj3028
我的做法
再来一道给各位熟悉一下泰勒展开:poj3734
生成函数做法
现在已知 f ( x ) = ∑ i = 0 n a i x i f(x)=\sum_{i=0}^na_ix^i f(x)=∑i=0naixi与 m m m个数 b 1 , b 2 , . . . , b m b_1,b_2,...,b_m b1,b2,...,bm。
求 f ( b 1 ) , f ( b 2 ) , . . . , f ( b m ) f(b_1),f(b_2),...,f(b_m) f(b1),f(b2),...,f(bm)
有很显然的 O ( n m ) O(nm) O(nm)暴力做法,这里直接略过。
考虑构造一个函数 g l , r ( x ) = ∏ i = l r ( x − b i ) g_{l,r}(x)=\prod_{i=l}^r(x-b_i) gl,r(x)=∏i=lr(x−bi)
那么 ∀ x 0 ∈ [ l , r ] \forall x_0\in[l,r] ∀x0∈[l,r],有 f ( x 0 ) = ( f % g l , r ) ( x 0 ) f(x_0)=(f\%g_{l,r})(x_0) f(x0)=(f%gl,r)(x0)
证明:
令 f ( x ) = g l , r ( x ) ∗ B ( x ) + R ( x ) = ( x − x 0 ) ∗ ( g l , r ( x ) x − x 0 ∗ B ( x ) ) + R ( x ) f(x)=g_{l,r}(x)*B(x)+R(x)=(x-x_0)*(\frac{g_{l,r}(x)}{x-x_0}*B(x))+R(x) f(x)=gl,r(x)∗B(x)+R(x)=(x−x0)∗(x−x0gl,r(x)∗B(x))+R(x)
当 x = x 0 x=x_0 x=x0时: f ( x 0 ) = 0 + R ( x 0 ) = R ( x 0 ) = ( f % g l , r ) ( x 0 ) f(x_0)=0+R(x_0)=R(x_0)=(f\%g_{l,r})(x_0) f(x0)=0+R(x0)=R(x0)=(f%gl,r)(x0)
有了这个结论就可以分治处理了。
int n,m,b[N],ans[N];
poly T[N<<2];
#define lc (p<<1)
#define rc (p<<1|1)
#define mid (l+r>>1)
inline void build(int p,int l,int r){
if(l==r){T[p]=poly(1,1),T[p][0]=mod-b[l];return;}
build(lc,l,mid),build(rc,mid+1,r);
T[p]=T[lc]*T[rc];
}
inline void solve(int p,int l,int r,poly t){
if(l==r){cout<<t.query(b[l])<<'\n';return;}
solve(lc,l,mid,t%T[lc]),solve(rc,mid+1,r,t%T[rc]);
}
#undef lc
#undef rc
#undef mid
int main(){
n=read(),m=read();
ntt_init();
poly a(n);
for(ri i=0;i<=n;++i)a[i]=read();
for(ri i=1;i<=m;++i)b[i]=read();
build(1,1,m);
solve(1,1,m,a%T[1]);
return 0;
}
给出 n n n个点 ( x i , y i ) (x_i,y_i) (xi,yi),求对应多项式。
有一种叫做拉格朗日插值的东西需要了解一下。
并且要会上面的多项式多点求值。
仔细观察拉格朗日插值的代数式:
f ( x ) = ∑ i = 1 n y i ∏ j = ̸ i x − x j x i − x j f(x)=\sum_{i=1}^ny_i\prod_{j=\not i}\frac{x-x_j}{x_i-x_j}\\ f(x)=i=1∑nyij≠i∏xi−xjx−xj
考虑如何快速求 v a l i = ∏ j = ̸ i ( x i − x j ) val_i=\prod_{j=\not i}(x_i-x_j) vali=∏j≠i(xi−xj)
构造函数 M ( x ) = ∏ i ( x − x i ) M(x)=\prod_i(x-x_i) M(x)=∏i(x−xi)
则 v a l i = lim x → x i M ( x ) x − x i = lim x → x i M ′ ( x ) ( x − x i ) ′ = lim x → x i M ′ ( x ) = M ′ ( x i ) val_i=\lim\limits_{x\rightarrow x_i}\frac{M(x)}{x-x_i}=\lim\limits_{x\rightarrow x_i}\frac{M'(x)}{(x-x_i)'}=\lim\limits_{x\rightarrow x_i}M'(x)=M'(x_i) vali=x→xilimx−xiM(x)=x→xilim(x−xi)′M′(x)=x→xilimM′(x)=M′(xi)
于是我们可以用多点求值求出 v a l val val数组。
现在讲拉格朗日插值的代数式恒等变形:
f ( x ) = ∑ i = 1 n y i ∏ j = ̸ i x − x j x i − x j = ∑ i = 1 n y i v a l i ∏ j = ̸ i ( x − x j ) = ( ∑ i = 1 m i d y i v a l i ∏ j = ̸ i ( x − x j ) ) ( ∏ i = m i d + 1 n ( x − x i ) ) + ( ∑ i = m i d + 1 n y i v a l i ∏ j = ̸ i ( x − x j ) ) ( ∏ i = 1 m i d ( x − x i ) ) f(x)=\sum_{i=1}^ny_i\prod_{j=\not i}\frac{x-x_j}{x_i-x_j}\\ \ \ \ \ \ \ \ \ \ \ \ \ \ =\sum_{i=1}^n\frac{y_i}{val_i}\prod_{j=\not i}(x-x_j)\\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ =(\sum_{i=1}^{mid}\frac{y_i}{val_i}\prod_{j=\not i}(x-x_j))(\prod_{i=mid+1}^n(x-x_i))\\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ +(\sum_{i=mid+1}^{n}\frac{y_i}{val_i}\prod_{j=\not i}(x-x_j))(\prod_{i=1}^{mid}(x-x_i)) f(x)=i=1∑nyij≠i∏xi−xjx−xj =i=1∑nvaliyij≠i∏(x−xj) =(i=1∑midvaliyij≠i∏(x−xj))(i=mid+1∏n(x−xi)) +(i=mid+1∑nvaliyij≠i∏(x−xj))(i=1∏mid(x−xi))
用分治 n t t ntt ntt解决即可。
代码:
#define lc (p<<1)
#define rc (p<<1|1)
#define mid (l+r>>1)
poly T[N<<2];
int n,fx[N],fy[N],val[N];
inline void build(int p,int l,int r){
if(l==r){T[p]=poly(1,1),T[p][0]=mod-fx[l];return;}
build(lc,l,mid),build(rc,mid+1,r);
T[p]=T[lc]*T[rc];
}
inline void getval(int p,int l,int r,poly t){
if(l==r){
val[l]=mul(ksm(t.getval(fx[l]),mod-2),fy[l]);
return;
}
getval(lc,l,mid,t%T[lc]),getval(rc,mid+1,r,t%T[rc]);
}
inline poly dc_ntt(int p,int l,int r){
if(l==r)return poly(0,val[l]);
poly L=dc_ntt(lc,l,mid),R=dc_ntt(rc,mid+1,r);
return L*T[rc]+T[lc]*R;
}
#undef lc
#undef rc
#undef mid
int main(){
n=read();
ntt_init();
for(ri i=1;i<=n;++i)fx[i]=read(),fy[i]=read();
build(1,1,n);
getval(1,1,n,poly_direv(T[1]));
poly f=dc_ntt(1,1,n);
for(ri i=0;i<n;++i)cout<<f[i]<<' ';
return 0;
}
考虑求出如下两个多项式的乘积:
A ( x ) = ∑ i = 0 n a i x i ‾ , B ( x ) = ∑ i = 0 m b i x i ‾ A(x)=\sum_{i=0}^na_ix^{\underline i},B(x)=\sum_{i=0}^mb_ix^{\underline i} A(x)=∑i=0naixi,B(x)=∑i=0mbixi
然后考虑把它们转成点值形式然后乘起来再转回去。
设现在 F ( x ) = ∑ i = 0 n f i x i ‾ , F ^ ( x ) F(x)=\sum_{i=0}^nf_ix^{\underline i},\hat F(x) F(x)=∑i=0nfixi,F^(x)为其点值的生成函数。
现在先考虑 x n ‾ x^{\underline n} xn的 E G F EGF EGF
= ∑ i = 0 ∞ i n ‾ i ! x i =\sum_{i=0}^{\infin}\frac{i^{\underline n}}{i!}x^i =i=0∑∞i!inxi
= ∑ i = 0 ∞ 1 ( i − n ) ! x i =\sum_{i=0}^{\infin}\frac{1}{(i-n)!}x^i =i=0∑∞(i−n)!1xi
= ∑ i = n ∞ 1 i ! x i + n =\sum_{i=n}^{\infin}\frac{1}i!x^{i+n} =i=n∑∞i1!xi+n
= x n e x =x^ne^x =xnex
然后有
F ^ ( x ) = ∑ i = 0 ∞ F ( i ) i ! x i \hat F(x)=\sum_{i=0}^{\infin}\frac{F(i)}{i!}x^i F^(x)=i=0∑∞i!F(i)xi
F ^ ( x ) = ∑ i = 0 ∞ x i i ! ∑ j = 0 ∞ f j j i ‾ \hat F(x)=\sum_{i=0}^{\infin}\frac{x^i}{i!}\sum_{j=0}^{\infin}f_jj^{\underline i} F^(x)=i=0∑∞i!xij=0∑∞fjji
F ^ ( x ) = ∑ i = 0 ∞ f i ∑ j = 0 ∞ x j j ! i j ‾ \hat F(x)=\sum_{i=0}^{\infin}f_i\sum_{j=0}^{\infin}\frac{x^j}{j!}i^{\underline j} F^(x)=i=0∑∞fij=0∑∞j!xjij
F ^ ( x ) = ∑ i = 0 ∞ f x i e x \hat F(x)=\sum_{i=0}^{\infin}f_x^ie^x F^(x)=i=0∑∞fxiex
F ^ ( x ) = e x ∑ i = 0 ∞ f i x i \hat F(x)=e^x\sum_{i=0}^{\infin}f_ix^i F^(x)=exi=0∑∞fixi
于是就转化成了常规多项式乘法问题。
我们把 ∑ i = 0 n f i x i \sum_{i=0}^{n}f_ix^i ∑i=0nfixi这个多项式与 e x e^x ex相乘就能得到点值的生成函数,同理,点值的生成函数乘上 e − x e^{-x} e−x就能还原多项式。
因此可以结合 n t t / ntt/ ntt/暴力多项式乘法在 O ( n log n ) / O ( n 2 ) O(n\log n)/O(n^2) O(nlogn)/O(n2)的时间内完成下降幂多项式乘法。
代码:
inline void ffp(poly&a,const int&type){
int n;
poly t=ifac.extend(n=a.deg());
if(type<0)for(ri i=0;i<=n;++i)t[i]=i&1?mod-t[i]:t[i];
a=(a*t).extend(n);
}
已知普通多项式 A ( x ) = ∑ i = 0 n a i x i A(x)=\sum_{i=0}^na_ix^i A(x)=∑i=0naixi
现在求下降幂多项式 B ( x ) = ∑ i = 0 n b i x i ‾ B(x)=\sum_{i=0}^nb_ix^{\underline i} B(x)=∑i=0nbixi使得 A ( x ) = B ( x ) A(x)=B(x) A(x)=B(x)
思路:
考虑先多点求值,然后 i f f p iffp iffp即可求出对应的下降幂多项式。
已知下降幂多项式 A ( x ) = ∑ i = 0 n a i x i ‾ A(x)=\sum_{i=0}^na_ix^{\underline i} A(x)=∑i=0naixi
现在求普通多项式 B ( x ) = ∑ i = 0 n b i x i B(x)=\sum_{i=0}^nb_ix^i B(x)=∑i=0nbixi使得 A ( x ) = B ( x ) A(x)=B(x) A(x)=B(x)
思路:
考虑先 f f p ffp ffp转点值,然后快速插值即可求出对应的普通多项式。
cc PRIMEDST 点分治+ f f t fft fft优化合并
bzoj4259 f f t fft fft套路题
spoj Triple Sums f f t fft fft+容斥原理
bzoj4827 推式子练习
hdu5829 推式子练习
洛谷4725/4726 多项式取对/exp
bzoj5300 fft优化高精
洛谷P4512 多项式除法
bzoj3513 fft+简单容斥
codeforces528D fft处理字符串匹配(有特殊条件)
bzoj3027 生成函数+爆搜
bzoj3771 生成函数+容斥
noip训练 生成函数+二项式展开
bzoj3992 生成函数+ntt+快速幂
bzoj4001 生成函数+求导推导
bzoj3625 生成函数+多项式求逆+多项式开方
noip训练 生成函数简单推导
poj1322 生成函数+二项式展开
bzoj3456 生成函数+多项式求逆
codeforces632E 生成函数+快速幂
codeforces1096G 生成函数+快速幂