【科技】快速莫比乌斯变换(反演) 与 子集卷积
我们比较了解的是有关多项式的乘法运算,对于下标为整数,下标运算为相加等于某个数的时候,我们有很优秀的FFT做法。
但是遇到一些奇怪的卷积形式时,比如我们定义 , 。
此时下标是一个集合,运算为集合并的卷积,我们已知了 和 ,需要快速算出 。
最暴力的做法是 分别枚举 和 ,把答案加到 中去,这样复杂度是 ,不太行。
这时我们就要一种高效的算法。求卷积可以用分治乘法,好像比较高妙,但我们要讲的是另一种:快速莫比乌斯变换和反演。
类比FFT,FMT也需要先把 和 求点值,点值相乘后再插值回去,快速莫比乌斯变换就相当于点值,快速莫比乌斯反演就相当于插值。
具体证明:
- 我们定义 的莫比乌斯变换为 ,其中 。
- 相反的,我们定义 的莫比乌斯反演为 ,其中 ,用容斥原理易得。
- 然后我们对卷积式两边同时做莫比乌斯变换:。
- 由于 ,所以 。
- 即 。
于是问题就在于如何快速求出 和 莫比乌斯变换(反演)。
如果要暴力的话,可以直接枚举子集算出莫比乌斯变换(反演),这样是 ,虽然比较优秀了,但复杂度还能更低。
我们考虑用递推解决:
- 设 表示
- 易得初始状态:
- 对于每一个不包含 的集合 ,可知 (因为 并没有 这位),(前者的 没有包含 ,而后者的 必须包含了 )。
- 显然,递推了 轮之后, 就是所求的变换了。
这样我们就能在 快速求出 的莫比乌斯变换了。(逆莫比乌斯变换同理)
于是我们就解决了集合并卷积的问题。
UPD:
我们都知道第一层循环枚举集合,第二层循环枚举它为的位,把去掉这个的子集的答案加上去的做法是错的。我们考虑两个集合,其中。可能有多种路径到达,也就是存在多个,这样就会被算多次。
这里有一个感性理解的方法,为什么第一层枚举位第二层枚举集合是对的,也就是每一个集合它的所有子集的贡献只被算了一次。
我们假设为并上第一个和不一样的位,我们发现的答案会先算到上,而对于其他的,在的答案算上来的时候自己的答案已经会先算上去了。
而对于逆莫比乌斯变换,如果理解了莫比乌斯变换后,其本质就是一个容斥。
void Fmt(int *a) { for (int i = 0; i < n; ++i) for (int s = 0; s < U; ++s) if (s >> i & 1) a[s] = Add(a[s], a[s ^ (1 << i)]); }
void Ifmt(int *a) { for (int i = 0; i < n; ++i) for (int s = 0; s < U; ++s) if (s >> i & 1) a[s] = Sub(a[s], a[s ^ (1 << i)]); }
(此处为集合大小, )
接下来我们来继续讲一讲子集卷积:
问题是已知 和 ,我们想求出 ,其中 。
回顾刚刚的集合并卷积,子集卷积的条件比集合并卷积更苛刻,即 和 的集合应该不相交。
考虑集合并卷积合法当且仅当 ,我们可以在卷积时多加一维,维护集合的大小,如 表示集合中有 个元素,集合表示为 。可以发现当 和 的真实元素个数符合时才是对的。
初始时,我们只把 的值赋成原来的 ( 同理),然后对每一个做一遍FMT,点值相乘时这么写:。最后扫一遍把不符合实际情况的状态赋成 即可。(表示集合元素个数,即)
for i = 0 to n Fmt(f[i]) Fmt(g[i]) for s = 0 to U - 1 for j = 0 to i h[i][s] += f[j][s] * g[i - j][s] Ifmt(h[i]) for s = 0 to U - 1 h[bc[s]][s] is the real answer
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】