【学习笔记】多项式 2:集合幂级数
集合幂级数#
定义#
定义集合 , 表示 的所有子集构成的集合。
定义从集合到数值的映射 ,则集合幂级数 , 只是占位符,用来表示 对应位置,并无实际意义。
运算#
加法运算同正常形式幂级数相同,对应位置加和即可。
乘法运算的系数仍然是对应相乘,而下标的运算则是按照一种关于集合的运算 。
对于某一项而言:
于是整体:
应用#
多数情况下我们将状态压成二进制并卷积进行计算,因此应用较广的是以位运算作为 ,也称为 位运算卷积。
子集相关运算#
高维前(后)缀和#
对于第一个式子,枚举当前位置 ,假设已知前 位的和,即 得到的贡献 中 在前 位是 的子集,剩余位置与 相同,计算第 位实际上是这一位是 的答案贡献到是 的位置。
而第二个式子则是 到 的贡献。
//前缀
for(int k=0;k<n;++k){
for(int i=0;i<(1<<n);++i){
if(i&(1<<k)) f[i]+=f[i^(1<<k)];
}
}
//后缀
for(int k=0;k<n;++k){
for(int i=0;i<(1<<n);++i){
if(!(i&(1<<k))) f[i]+=f[i^(1<<k)];
}
}
高维前(后)缀差分#
子集和超集反演之后,得到:
只需要在转移时增加一个系数 ,至于没有次数的原因,考虑 到 的靠拢过程应当是增补 (作为子集)或削去 (作为超集),这样转移次数就是两个集合大小之差,每次转移乘 ,正符合上面式子的次数。
//前缀
for(int k=0;k<n;++k){
for(int i=0;i<(1<<n);++i){
if(i&(1<<k)) f[i]-=f[i^(1<<k)];
}
}
//后缀
for(int k=0;k<n;++k){
for(int i=0;i<(1<<n);++i){
if(!(i&(1<<k))) f[i]-=f[i^(1<<k)];
}
}
快速莫比乌斯变换 FMT#
或卷积(集合并卷积)#
考虑构造 ,则 。
于是做法是先构造出 与 ,乘出 ,再逆变换回 ,这个逆变换称为 FMI。
与卷积(集合交卷积)#
考虑构造 ,则 。
把或卷积稍微改变一下。
点击查看代码
int n;
inline void FMT_or(int *f,int c){
for(int k=0;k<n;++k){
for(int i=0;i<(1<<n);++i){
if(i&(1<<k)) f[i]=(f[i]+1ll*c*f[i^(1<<k)]%mod)%mod;
}
}
}
inline void FMT_and(int *f,int c){
for(int k=0;k<n;++k){
for(int i=0;i<(1<<n);++i){
if(!(i&(1<<k))) f[i]=(f[i]+1ll*c*f[i^(1<<k)]%mod)%mod;
}
}
}
int a[maxn],b[maxn];
int F[maxn],G[maxn],H[maxn];
int main(){
n=read();
for(int i=0;i<(1<<n);++i) a[i]=read();
for(int i=0;i<(1<<n);++i) b[i]=read();
for(int i=0;i<(1<<n);++i) F[i]=a[i],G[i]=b[i];
FMT_or(F,1),FMT_or(G,1);
for(int i=0;i<(1<<n);++i) H[i]=1ll*F[i]*G[i]%mod;
FMT_or(H,mod-1);
for(int i=0;i<(1<<n);++i) printf("%d ",H[i]);
printf("\n");
for(int i=0;i<(1<<n);++i) F[i]=a[i],G[i]=b[i];
FMT_and(F,1),FMT_and(G,1);
for(int i=0;i<(1<<n);++i) H[i]=1ll*F[i]*G[i]%mod;
FMT_and(H,mod-1);
for(int i=0;i<(1<<n);++i) printf("%d ",H[i]);
printf("\n");
return 0;
}
特殊性质#
注意到与多项式乘法中的 FFT 不同的是,FMT 是一个纯粹的线性求和,因此经过若干次运算后规模仍然是 。
这使得我们在进行多次乘法时,可以全部 FMT 后求积再 FMI,而不必要像 FFT 一样每次都在系数与点值之间来回切换。
快速沃尔什变换 FWT#
异或卷积(集合对称差卷积)#
先证明一个定理:
证明只需考虑 的某个元素 ,除 外完全相同的两个 贡献和为 ,空集除外。
这样要求:
仿照上面推导式子的方法,设 ,代入得:
也就顺理成章地得到了 FWT 以及 IFWT 的基本式子,而二者只差了一个系数。
接下来继续模仿上面卷积时的递推方法,假定已经处理了前 位(不同于前面的子集与超集,这里前 位表示只有前 位有不同), 与 二者只在第 位不同,且 ,则二者经过某种运算一定可以计算出前 位的答案。
考虑对于 而言由 扩展到 ,对交集大小这个次数没有任何贡献,因此直接求和即可;而对于 而言,当且仅当在第 位能交集出 时,才会产生负贡献,而负贡献就来自于前 位的 ,写成表达式:
更为形式化的说,枚举至 时,令 ,则:
也只有最后一项的两个集合会多出一个 而取到负值。
点击查看代码
inline int q_pow(int A,int B,int P){
int res=1;
while(B){
if(B&1) res=1ll*res*A%P;
A=1ll*A*A%P;
B>>=1;
}
return res;
}
int n;
inline void FWT_xor(int *f,bool type){
for(int d=1;d<(1<<n);d<<=1){
for(int i=0;i<(1<<n);i+=d<<1){
for(int j=0;j<d;++j){
int x=f[i+j],y=f[i+d+j];
f[i+j]=(x+y)%mod,f[i+d+j]=(x-y+mod)%mod;
}
}
}
if(!type){
int inv=q_pow(1<<n,mod-2,mod);
for(int i=0;i<(1<<n);++i) f[i]=1ll*f[i]*inv%mod;
}
}
int F[maxn],G[maxn],H[maxn];
int main(){
n=read();
for(int i=0;i<(1<<n);++i) F[i]=read();
for(int i=0;i<(1<<n);++i) G[i]=read();
FWT_xor(F,1),FWT_xor(G,1);
for(int i=0;i<(1<<n);++i) H[i]=1ll*F[i]*G[i]%mod;
FWT_xor(H,0);
for(int i=0;i<(1<<n);++i) printf("%d ",H[i]);
printf("\n");
return 0;
}
子集卷积#
在或卷积的基础上,增加了新的限制:
实际上就是将 划分成两部分乘积再求和。
一个充要条件是 ,于是可以将无交修改为大小形如和卷积,即:
由于当且仅当第一维与第二维的 相等才能产生贡献,因此将初始值定在 处,其位置为 。
容易发现增加两个求和号以及第一维对 FMT 的推导过程没有影响,只是在计算乘积时要求贡献至 位置。
这样做 次 FMT 以及最后做 次 FMI 即可,复杂度 。
点击查看代码
int n;
inline void FMT_or(int *f,int c){
for(int k=0;k<n;++k){
for(int i=0;i<(1<<n);++i){
if(i&(1<<k)) f[i]=(f[i]+1ll*c*f[i^(1<<k)]%mod)%mod;
}
}
}
#define lowbit(x) (x&-x)
int popcount[maxn];
int F[21][maxn],G[21][maxn],H[21][maxn];
int main(){
n=read();
for(int i=1;i<(1<<n);++i) popcount[i]=popcount[i^lowbit(i)]+1;
for(int i=0;i<(1<<n);++i) F[popcount[i]][i]=read();
for(int i=0;i<(1<<n);++i) G[popcount[i]][i]=read();
for(int i=0;i<=n;++i) FMT_or(F[i],1),FMT_or(G[i],1);
for(int i=0;i<=n;++i){
for(int j=0;i+j<=n;++j){
for(int k=0;k<(1<<n);++k){
H[i+j][k]=(H[i+j][k]+1ll*F[i][k]*G[j][k])%mod;
}
}
}
for(int i=0;i<=n;++i) FMT_or(H[i],mod-1);
for(int i=0;i<(1<<n);++i) printf("%d ",H[popcount[i]][i]);
printf("\n");
return 0;
}
参考资料#
作者:SoyTony
出处:https://www.cnblogs.com/SoyTony/p/Learning_Notes_about_Polynomial_2.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效