小学乘法知识(可能连载)

FFT

板子

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=2097160;
const int G=3,md=998244353;
int n,m,i,j,k,u,v,p,t,le;
int f[maxn],g[maxn],r[maxn];
inline int Pow(int d,int z){
	int r=1;
	do{
		if(z&1) r=1LL*r*d%md;
		d=1LL*d*d%md;
	}while(z>>=1); return r;
}
inline void Add(int &x,int y){x-=((x+=y)>=md)*md;}
void dft(int *f){
	for(i=1;i<t;++i) if(i<r[i]) swap(f[i],f[r[i]]);
	for(i=1,le=2;le<=t;i=le,le<<=1){
		for(j=0,v=Pow(G,(md-1)/le);j<t;j+=le){
			for(k=0,u=1;k<i;++k,u=1LL*u*v%md){
				p=1LL*f[i+j+k]*u%md;
				Add(f[i+j+k]=f[j+k],md-p);
				Add(f[j+k],p);
			}
		}
	}
}
int main(){
	scanf("%d%d",&n,&m); 
	for(i=0;i<=n;++i) scanf("%d",f+i);
	for(i=0;i<=m;++i) scanf("%d",g+i);
	for(n+=m,t=1;t<=n;t<<=1);
	for(i=1;i<t;++i) r[i]=(r[i>>1]>>1)|((i&1)*(t>>1));
	dft(f); dft(g); for(i=0;i<t;++i) f[i]=1LL*f[i]*g[i]%md;
	dft(f); reverse(f+1,f+t); p=Pow(t,md-2);
	for(i=0;i<=n;++i) printf("%d ",1LL*f[i]*p%md);
	return 0;
}

属于需要背的知识。原根表。常见的原根形如 \(998244353\) 原根为 \(3\)\(924844033\) 原根为 \(5\)

分治 FFT

例题:P4721 【模板】分治 FFT

\(m=\left\lfloor\frac{l+r}2\right\rfloor\)。考虑在知道 \(f_l\sim f_m\) 时,其对 \(f_{m+1}\sim f_r\) 会造成怎样的贡献。此时 \(\forall x\in[m+1,r];f_x\gets\sum_{i=l}^mf_ig_{x-i}\),此时将 \(f_l\sim f_m\)\(g_1\sim g_{r-l}\) 卷积可得对应的贡献,分治下去计算其他部分的贡献即可。

使用多项式求逆:考虑 \(f,g\) 的生成函数 \(F(x)=\sum_{i\ge 0}f_ix^i,G(x)=\sum_{i\ge 0}g_ix^i\),则由 \(\forall i>0,f_i=\sum_{j=0}^{i-1}f_jg_{i-j}\)

\[\begin{aligned}F(x)&=f_0x^0+\sum_{i>0}x^i\sum_{j=0}^{i-1}f_jg_{i-j}\\&=1+x\sum_{i\ge 0}x^i\sum_{j=0}^i f_jg_{i+1-j}\\&=1+xF(x)\frac{G(x)-g_0}x\\&=1+F(x)(G(x)-g_0)\end{aligned} \]

由于 \(g_0\) 没有对 \(f\) 产生贡献,所以可以令 \(g_0=0\),则 \(F(x)=1+F(x)G(x)=\frac 1{1-G(x)}\)。可以多项式求逆解决。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=100010;
const int maxb=262150;
const int md=998244353,_g=3;
int n,i,j,k,u,v,t,p,le,f[maxn],g[maxn];
int r[maxb],F[maxb],G[maxb];
inline int Pow(int d,int z){
	int r=1;
	do{
		if(z&1) r=1LL*r*d%md;
		d=1LL*d*d%md; 
	}while(z>>=1); return r;
}
inline void Add(int &x,int y){x-=((x+=y)>=md)*md;} 
void dft(int *f){
	for(i=1;i<t;++i) if(i<r[i]) swap(f[i],f[r[i]]);
	for(i=1,le=2;le<=t;i=le,le<<=1){
		v=Pow(_g,(md-1)/le);
		for(j=0;j<t;j+=le){
			for(k=0,u=1;k<i;++k,u=1LL*u*v%md){
				p=1LL*f[i+j+k]*u%md;
				Add(f[i+j+k]=f[j+k],md-p);
				Add(f[j+k],p);
			} 
		}
	}
}
void Solve(int l,int r){
	if(l==r) return; int m=(l+r)>>1; Solve(l,m); 
	for(t=1,k=r-l+m-l-1;t<=k;t<<=1);
	memset(F,0,t<<2); memset(G,0,t<<2);
	memcpy(F,f+l,(m-l+1)<<2); memcpy(G,g+1,(r-l)<<2); 
	for(i=1;i<t;++i) ::r[i]=(::r[i>>1]>>1)|((i&1)*(t>>1));
	dft(F); dft(G); for(i=0;i<t;++i) F[i]=(1LL*F[i]*G[i])%md;
	dft(F); reverse(F+1,F+t); p=Pow(t,md-2);
	for(i=m+1;i<=r;++i) Add(f[i],1LL*F[i-l-1]*p%md); Solve(m+1,r);
}
int main(){
	scanf("%d",&n); --n; f[0]=1;
	for(i=1;i<=n;++i) scanf("%d",g+i); Solve(0,n); 
	for(i=0;i<=n;++i) printf("%d ",f[i]);
	return 0;
}

多项式牛顿迭代


引入:导数

导数表

\((a)'=0\)(其中 \(a\) 表示和 \(x\) 无关的常数,下同)
\((x^a)'=ax^{a-1}\)
\((a^x)'=a^x\ln a\)
\((\log_ax)'=\frac 1{x\ln a}\)
\((\sin x)'=\cos x\)
\((\cos x)'=-\sin x\)

导数/积分运算法则

乘法法则\((f(x)g(x))'=f'(x)g(x)+f(x)g'(x)\)
除法法则\(\left(\frac{f(x)}{g(x)}\right)'=\frac{f'(x)g(x)-f(x)g'(x)}{g^2(x)}\)
链式法则\(\frac{d}{dx}\frac{dx}{dy}=\frac{d}{dy}\)
复合函数求导\((g(f(x)))'=g'(f(x))f'(x)\)
分部积分\(fg=\int f'gdx+\int fg'dx\)
洛必达法则:如果 \(\lim_{x\to a}f(x)=0,\lim_{x\to a}g(x)=0\),且 \(f,g\)\(a\) 的某去心邻域内两者均可导且 \(g'(x)\ne 0\),则有 \(\lim_{x\to a}\frac{f(x)}{g(x)}=\lim_{x\to a}\frac{f'(x)}{g'(x)}\)。如果 \(\lim_{x\to a}f(x)=\infty,\lim_{x\to a}g(x)=\infty\),且 \(f,g\)\(a\) 的某去心邻域内两者均可导且 \(g'(x)\ne 0\),则有 \(\lim_{x\to a^+}\frac{f(x)}{g(x)}=\lim_{x\to a^+}\frac{f'(x)}{g'(x)}\)

引入:泰勒展开

在逼*某个函数 \(f(x)\) 时,可以考虑使用某个多项式,通过对 \(f(x)\) 在某个位置 \(x_0\)\(1\sim n\) 阶导数求出在同样位置处 \(1\sim n\) 阶导数相同的多项式,从而逼* \(f\),由 \((\sum_{i=0}^na_ix^i)'=\sum_{i=1}^nia_ix^{i-1}\)\(f(x)=\sum_{i\ge 0}\frac{f^{(i)}(x_0)}{i!}(x-x_0)^i\)。某些时候可以直接取 \(x_0=0\),则有 \(f(x)=\sum_{i\ge 0}\frac{f^{(i)}(0)}{i!}x^i\)一些例子

引入:牛顿迭代

在求解方程 \(f(x)=0\) 的解时,我们考虑使用靠*零点的切线逼*解。具体地,取函数的某个位置 \(x_0\),得出在 \(x_0\) 位置和 \(f(x)\) 相切的切线 \(y=f'(x_0)(x-x_0)+f(x_0)\)。此时取该切线的零点 \(x_1=x_0-\frac{f(x_0)}{f'(x_0)}\),然后再求出 \(x_1\) 位置和 \(f(x)\) 相切的切线 \(y=f'(x_1)(x-x_1)+f(x_1)\);就可以不断迭代下去以逼*该解(显然无法得到解的精确值)。

虽然某些时候不能用牛顿迭代法逼*解,但是这个算法有较为广泛的应用,例如 \(O(1)\) 求某个数的*似*方根。同时牛顿迭代法收敛速度(逼*正解速度)快,可以说每次迭代后有效数位均会上升一倍。


在已知多项式 \(g(x)\),求满足 \(g(f(x))\equiv 0\pmod {x^n}\)\(f(x)\bmod x^n\) 时,同样可以考虑牛顿迭代法的思路。设已经求出了 \(\bmod x^{\left\lceil\frac n2\right\rceil}\) 部分的解 \(f_0(x)\),(注意需要单独求出常数项)则求 \(f(x)\) 时可以考虑将 \(g\)\(f_0(x)\) 处泰勒展开得:

\[\sum_{i\ge 0}\frac{g^{(i)}(f(x_0))}{i!}(f(x)-f_0(x))^i\equiv 0\pmod {x^n} \]

然后考虑 \(f_0(x)\) 的最高次数为 \(\left\lceil\frac n2\right\rceil-1\),所以 \(f(x)-f_0(x)\) 的最低次数非零项的次数为 \(\left\lceil\frac n2\right\rceil\),则上式 \(\bmod x^n\) 等效于 \(g(f_0(x))+g'(f_0(x))(f(x)-f_0(x))\bmod x^n\),解得 \(f(x)=f_0(x)-\frac{g(f_0(x))}{g'(f_0(x))}\)

多项式牛顿迭代可以用于求多项式经过某些运算后的值。例如求多项式 \(h(x)\) 的逆时可以构造 \(g(f(x))=\frac 1{f(x)}-h(x)\),开方时可以构造 \(g(f(x))=f^2(x)-h(x)\),求 \(\exp\) 时可以构造 \(g(f(x))=\ln f(x)-h(x)\)

多项式求逆

构造 \(g(f(x))=\frac 1{f(x)}-h(x)\),在求出上述的 \(f_0(x)\) 后,由于 \(g'(f(x))=-\frac 1{f^2(x)}\),则有 \(f(x)=f_0(x)-\frac{\frac 1{f_0(x)}-h(x)}{-\frac 1{f_0^2(x)}}=2f_0(x)-f_0^2(x)h(x)\)

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxb=262150;
const int md=998244353,G=3;
int n,i,j,k,u,v,p,d,t,le;
int f[maxb],g[maxb],h[maxb],r[maxb],x[maxb];
inline int Pow(int d,int z){
	int r=1;
	do{
		if(z&1) r=1LL*r*d%md;
		d=1LL*d*d%md;
	}while(z>>=1); return r;
}
inline void Add(int &x,int y){x-=((x+=y)>=md)*md;}
void dft(int *f){
	for(i=1;i<t;++i) if(i<r[i]) swap(f[i],f[r[i]]);
	for(i=1,le=2;le<=t;i=le,le<<=1){
		for(j=0,v=Pow(G,(md-1)/le);j<t;j+=le){
			for(k=0,u=1;k<i;++k,u=1LL*u*v%md){
				p=1LL*f[i+j+k]*u%md;
				Add(f[i+j+k]=f[j+k],md-p);
				Add(f[j+k],p);
			}
		}
	}
}
int main(){
	scanf("%d",&n);
	for(i=0;i<n;++i) scanf("%d",h+i); n<<=1;
	f[0]=Pow(h[0],md-2); Add(x[0]=f[0],f[0]);
	for(d=2,t=4;d<=n;d=t,t<<=1){
		for(i=0;i<t;++i) r[i]=(r[i>>1]>>1)+(i&1)*d;
		memcpy(g,h,d<<2); dft(g); dft(f); 
		for(i=0;i<t;++i) f[i]=1LL*f[i]*f[i]%md*g[i]%md;
		dft(f); reverse(f+1,f+t); p=Pow(t,md-2); 
		for(i=0;i<d;++i){
			Add(f[i]=md-1LL*f[i]*p%md,x[i]);
			Add(x[i]=f[i],f[i]); f[i+d]=0;
		}
	}
	for(n>>=1,i=0;i<n;++i) printf("%d ",f[i]);
	return 0;
}

多项式 \(\ln\)

考虑 \(\ln'(x)=\frac 1x\)。此时我们需要求 \(g(x)=\ln f(x)\),两边同时求导有 \(g'(x)=(\ln f(x))'=\frac{f'(x)}{f(x)}\)。注意此时 \(g(x)=\int_0^x\frac{f'(t)}{f(t)}dt+\ln f(0)\),而 \(\rm e\) 为超越数(不能作为某个方程 \(\sum_i a_ix^i=0\) 的根,其中 \(a_i\) 均为整数,所以不能在模意义下被表示),所以 \(\ln f(0)\)\(f(0)\) 不为 \(1\) 时不能在模意义下被表示。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxb=262150;
const int md=998244353,G=3;
int n,i,j,k,u,v,d,t,p,le;
int f[maxb],g[maxb],h[maxb],x[maxb],r[maxb];
inline int Pow(int d,int z){
	int r=1;
	do{
		if(z&1) r=1LL*r*d%md;
		d=1LL*d*d%md;
	}while(z>>=1); return r;
}
inline void Add(int &x,int y){x-=((x+=y)>=md)*md;}
void dft(int *f){
	for(i=1;i<t;++i) if(i<r[i]) swap(f[i],f[r[i]]);
	for(i=1,le=2;le<=t;i=le,le<<=1){
		for(j=0,v=Pow(G,(md-1)/le);j<t;j+=le){
			for(k=0,u=1;k<i;++k,u=1LL*u*v%md){
				p=1LL*f[i+j+k]*u%md;
				Add(f[i+j+k]=f[j+k],md-p);
				Add(f[j+k],p);
			}
		}
	}
}
int main(){
	scanf("%d",&n);
	for(i=0;i<n;++i) scanf("%d",h+i); n<<=1;
	for(f[0]=1,d=x[0]=2,t=4;d<=n;d=t,t<<=1){
		for(i=0;i<t;++i) r[i]=(r[i>>1]>>1)+(i&1)*d;
		memcpy(g,h,d<<2); dft(f); dft(g);
		for(i=0;i<t;++i) f[i]=1LL*f[i]*f[i]%md*g[i]%md;
		dft(f); reverse(f+1,f+t); p=Pow(t,md-2);
		for(i=0;i<d;++i){
			Add(f[i]=md-1LL*f[i]*p%md,x[i]);
			Add(x[i]=f[i],f[i]); f[i+d]=0;
		}
	}
	for(n>>=1,i=1;i<n;++i) h[i-1]=1LL*h[i]*i%md; h[n]=0;
	memset(f+n,0,(d-n)<<2); for(t=1;t<=(n-1)<<1;t<<=1);
	for(i=0;i<t;++i) r[i]=(r[i>>1]>>1)+(i&1)*(t>>1);
	dft(f); dft(h); for(i=0;i<t;++i) f[i]=1LL*f[i]*h[i]%md;
	dft(f); reverse(f+1,f+t); p=Pow(t,md-2); 
	for(i=0;i<t;++i) f[i]=1LL*f[i]*p%md;
	for(i=n-1;i;--i) f[i]=1LL*f[i-1]*Pow(i,md-2)%md; 
	for(f[0]=i=0;i<n;++i) printf("%d ",f[i]); return 0;
}

多项式 \(\exp\)

构造 \(g(f(x))=\ln f(x)-h(x)\),在求出上述的 \(f_0(x)\) 后,由于 \(g'(f(x))=\frac1{f(x)}\),则有 \(f(x)=f_0(x)-\frac{\ln f_0(x)-h(x)}{\frac1{f_0(x)}}=f_0(x)(1-\ln f_0(x)+h(x))\)

代码

懒得封装,写得比较丑。

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxb=262150;
const int maxt=524300;
const int md=998244353,G=3;
int n,i,j,k,u,v,p,t,le,d0,t0,d1;
int f0[maxb],f1[maxt],g1[maxb],g[maxt],h[maxb],x[maxt],r[maxt];
inline int Pow(int d,int z){
	int r=1;
	do{
		if(z&1) r=1LL*r*d%md;
		d=1LL*d*d%md;
	}while(z>>=1); return r;
}
inline void Add(int &x,int y){x-=((x+=y)>=md)*md;}
void dft(int *f){
	for(i=1;i<t;++i) if(i<r[i]) swap(f[i],f[r[i]]);
	for(i=1,le=2;le<=t;i=le,le<<=1){
		for(j=0,v=Pow(G,(md-1)/le);j<t;j+=le){
			for(k=0,u=1;k<i;++k,u=1LL*u*v%md){
				p=1LL*f[i+j+k]*u%md;
				Add(f[i+j+k]=f[j+k],md-p);
				Add(f[j+k],p);
			}
		}
	}
}
int main(){
	scanf("%d",&n); 
	for(i=1;i<n;++i) scanf("%d",h+i); n<<=1;
	for(f0[0]=1,d0=2,t0=4;d0<=n;d0=t0,t0<<=1){ //外层倍增求 exp
		for(i=1;i<d0;++i) g1[i-1]=1LL*f0[i]*i%md; //求导
		for(f1[0]=1,x[0]=d1=2,t=4;d1<=t0;d1=t,t<<=1){ //内层倍增求逆
			for(i=1;i<t;++i) r[i]=(r[i>>1]>>1)|((i&1)*d1);
			memcpy(g,f0,d1<<2); dft(f1); dft(g);
			for(i=0;i<t;++i) f1[i]=1LL*f1[i]*f1[i]%md*g[i]%md; 
			dft(f1); reverse(f1+1,f1+t); p=Pow(t,md-2);
			for(i=0;i<d1;++i){
				Add(f1[i]=md-1LL*f1[i]*p%md,x[i]);
				Add(x[i]=f1[i],f1[i]); f1[i+d1]=0; 
			} 
		}
		memset(f1+d0,0,(d1-d0)<<2); for(t=1;t<=t0-2;t<<=1);
		for(i=1;i<t;++i) r[i]=(r[i>>1]>>1)|((i&1)*(t>>1));
		dft(f1); dft(g1); for(i=0;i<t;++i) f1[i]=1LL*f1[i]*g1[i]%md; 
		dft(f1); reverse(f1+1,f1+t); p=Pow(t,md-2); 
		memset(f1+d0,0,(t-d0)<<2); memset(g1,0,t<<2);
		for(i=0;i<d0;++i) f1[i]=1LL*f1[i]*p%md; //f'(x)/f(x)
		for(i=d0-1;i;--i) f1[i]=1LL*f1[i-1]*Pow(i,md-2)%md; //积分
		for(f1[0]=i=1;i<d0;++i) f1[i]=(1LL*f1[i]*(md-1)+h[i])%md; 
		for(i=1,t=t0;i<t;++i) r[i]=(r[i>>1]>>1)|((i&1)*d0);
		dft(f0); dft(f1); for(i=0;i<t;++i) f0[i]=1LL*f0[i]*f1[i]%md;
		dft(f0); reverse(f0+1,f0+t); p=Pow(t,md-2);
		for(i=0;i<d0;++i) f0[i]=1LL*f0[i]*p%md; //求 exp
		memset(f0+d0,0,d0<<2); memset(f1,0,t<<2); memset(g,0,d1<<2); 
	}
	for(n>>=1,i=0;i<n;++i) printf("%d ",f0[i]); return 0;
}

附:\(O(n^2)/O(n\log^2 n)\) 求逆/ \(\ln\) / \(\exp\) 的做法

可以在多项式次数小的时候进行优化(\(\exp\) 的一种卡常就是分治 FFT 处理下面的 \(\exp\) 形式)时间/码长。

求逆:考虑 \((\sum_{i=0}^n f_ix^i)(\sum_{i=0}^n g_ix^i)=1\) 时由于 \(\forall i>1,\sum_{j=0}^i f_jg_{i-j}=0\),所以 \(g_i=-\frac{\sum_{j=0}^{i-1}g_jf_{i-j}}{f_0}\)

\(\ln/\exp\):令 \(F(x)=\sum_{i=0} f_ix^i,G(x)=\sum_{i=0} g_ix^i\)。考虑 \(F(x)=\ln G(x),G(x)=\exp F(x)\) 时(此时有 \(f_0=0,g_0=1\)),有:

\[F'(x)=\frac{G'(x)}{G(x)}\\F'(x)G(x)=G'(x)\\xF'(x)G(x)=xG'(x)\\\sum_{j=0}^ijf_jg_{i-j}=ig_i\\g_i=\frac 1i\sum_{j=1}^ijf_jg_{i-j}\\f_i=f_ig_0=g_i-\frac 1i\sum_{j=0}^{i-1}jf_jg_{i-j} \]

多项式带余除法(待补)

多项式多点求值(待补)

拉格朗日插值

在已知某个不超过 \(n\) 次的多项式上的 \(n\) 个点时,可以使用拉格朗日插值法确定出这个唯一的多项式。设第 \(i\) 个点为 \((x_i,y_i)\)

对于多项式 \(f(x)=\sum_{i=0}^N f_ix^i\),由 \(f(x)-f(a)=\sum_{i=0}^Nf_i(x^i-a^i)=\sum_{i=1}^N f_i(x-a)(\sum_{j=0}^{i-1}x^ja^{i-1-j})\)\(f(x)\equiv f(a)\pmod{x-a}\)。此时可以列出方程组如下:

\[\begin{cases}f(x)\equiv y_1\pmod{x-x_1}\\f(x)\equiv y_2\pmod{x-x_2}\\\cdots\\f(x)\equiv y_n\pmod{x-x_n}\end{cases} \]

考虑使用中国剩余定理(可以将所有 \(x-x_i\) 看成两两互质),令 \(A=\prod_{i=1}^n(x-x_i)\)\(t_i\) 为模 \(x-x_i\) 意义下 \(\frac A{x-x_i}\) 的逆元,由于 \(\frac{A}{x-x_i}=\prod_{j\ne i}(x-x_j)=\prod_{j\ne i}((x-x_i)+(x_i-x_j))\equiv\prod_{j\ne i}(x_i-x_j)\pmod{x-x_i}\),则 \(t_i=\prod_{j\ne i}\frac 1{x_i-x_j}\);答案为 \(\sum_{i=1}^ny_i\prod_{j\ne i}\frac{x-x_j}{x_i-x_j}\)(其对应次数小于 \(n\),所以在模 \(A\) 意义下解就是这个式子)。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=2010;
const int md=998244353;
int n,k,i,j,a,b,c,s,x[maxn],y[maxn];
inline int Pow(int d,int z){
	int r=1;
	do{
		if(z&1) r=1LL*r*d%md;
		d=1LL*d*d%md;
	}while(z>>=1); return r;
}
int main(){
	scanf("%d%d",&n,&k);
	for(i=1;i<=n;++i) scanf("%d%d",x+i,y+i);
	for(i=1;i<=n;++i,s=(1LL*a*b%md*Pow(c,md-2)+s)%md)
		for(a=y[i],j=b=c=1;j<=n;++j) if(j!=i)
			b=1LL*b*(k+md-x[j])%md,c=1LL*c*(x[i]+md-x[j])%md;
	printf("%d",s); return 0;
}

FMT/FWT(个人的理解)

此处用了一种较为清奇(?)的思路。

(1) 或卷积(求 \(C_i=\sum_{j\mathop{\rm or}k=i}A_jB_k\)

考虑多项式乘法的过程:我们会先对序列进行 DFT,然后相乘,最后进行 IDFT。此时可以使用类似的方式,用某种序列表示需要操作的序列。

\(o(A)_i=\sum_{x\mathop{\rm or}i=i}A_x\),则 \(o(C)_i=\sum_{(j\mathop{\rm or}k)\mathop{\rm or}i=i}A_jB_k\)。而 \(x\mathop{\rm or}i=i\) 等效于 \(x\)\(i\) 的子集,所以 \((j\mathop{\rm or}k)\mathop{\rm or}i\) 当且仅当 \(j,k\) 均是 \(i\) 的子集,也就是说 \(C_i=A_iB_i\)。求 \(o(A/B)\) 可以使用上述的方法,而从 \(o(C)\) 变回 \(C\) 时,考虑上述 dp 过程的逆过程:在从 \(dp_i\) 推到 \(dp_{i-1}\) 时,如果某个 \(S\) 的第 \(i\) 位为 \(1\)\(dp_{i,S}=dp_{i-1,S}+dp_{i-1,S\mathop{\rm xor}2^i}=dp_{i-1,S}+dp_{i,S\mathop{\rm xor}2^i}\),否则 \(dp_{i,S}=dp_{i-1,S}\),直接 dp 即可。

(2) 与卷积(求 \(C_i=\sum_{j\mathop{\rm and}k=i}A_jB_k\)

仍然可以考虑上述的过程,设 \(a(A)_i=\sum_{x\mathop{\rm and}i=i}A_x\),则同样有 \((j\mathop{\rm and}k)\mathop{\rm and}i=i\) 当且仅当 \(j\mathop{\rm and}i=k\mathop{\rm and}i=i\),所以仍然有 \(a(C)_i=a(A)_ia(B)_i\)。求 \(a(A)\) 时仍然可以使用上面 dp 的思路,但是转移时有 \(dp_{i,S}=dp_{i-1,S}+[S\mathop{\rm and}2^i=0]dp_{i-1,S\mathop{\rm or}2^i}\)。从 \(a(C)\) 推回 \(C\) 同理。

(3) 异或卷积(求 \(C_i=\sum_{j\mathop{\rm xor}k=i}A_jB_k\)

考虑异或的性质,令 \(c(x)\)\(x\) 在二进制表示下的位数,可以发现 \((c(j)+c(k)-c(j\mathop{\rm xor}k))\bmod 2=0\),同理有 \((c(j\mathop{\rm and}i)+c(k\mathop{\rm and}i)-c((j\mathop{\rm xor}k)\mathop{\rm and}i))\bmod 2=0\)。令 \(x_p(A)_i=\sum_{c(j\mathop{\rm and}i)\bmod 2=p}A_j\),则 \(x_p(C)_i=\sum_{j,k\in\{0,1\}}[j\mathop{\rm xor}k=p]x_j(A)_ix_k(B)_i\)\(x_{0/1}\) 也可以使用上述的 dp 方式计算。不过有更简洁的方式计算:设 \(x(A)_i=x_0(A)_i-x_1(A)_i\),则 \(x(C)_i=x(A)_ix(B)_i\)。同或卷积可以直接将求得的 \(C\) 翻转即可。

(4) 子集卷积(求 \(C_i=\sum_{j\mathop{\rm or}k=i,j\mathop{\rm and}k=0}A_jB_k\)

考虑第二个性质等效于 \(c(j)+c(k)=c(i)\)。可以把所有下标按照 \(c\) 的不同值分开处理,设 \(o_p(A)_i\)\(\sum_{c(j)=p,j\mathop{\rm and}i=i}A_j\),则 \(o_p(C)_i=\sum_{j+k=p}o_j(A)_io_k(B)_i\)(暴力计算即可,节省常数)。这里的 \(o(A)\) 被称为 \(A\)占位幂级数

集合幂级数进阶计算

考虑将上述的子集卷积看成乘法,并以此为基础定义求逆 / \(\ln\) / \(\exp\) 等运算。此时可以将子集卷积中对占位多项式的卷积操作改成多项式求逆 / \(\ln\) / \(\exp\),其他操作不变即可。此时集合幂级数的 \(\ln/\exp\) 和多项式的对应 \(\ln/\exp\) 的组合意义有相似之处;例如多项式 \(\exp\) 对应了将全集划分为有标号元素组成的无标号集合的方案,则集合幂级数 \(\exp\) 对应了将全集划分成若干子集(满足 子集不同 对应的方案就会不同)的对应方案。可以使用上述的 \(O(n^2)\) 求逆 / \(\ln\) / \(\exp\) 的做法节省常数。

posted @ 2023-02-20 15:33  Fran-Cen  阅读(64)  评论(0编辑  收藏  举报