如何在不能求逆的时候做子集卷积 exp(即便能求逆也比常见方法优雅)

为什么要求逆?正常做子集卷积 exp 的时候递推求 \(G=\exp(F)\) 的系数时要用。

什么情况下不能求逆?模 \(2^{64}\),或者压根不取模。

我们可能会想,算出来肯定除得尽啊,因为组合意义上是不会出现分数的。

并非如此,例如我们可能会尝试算 \(\exp(x)\cdot \exp(2x)\)\([x^3]\) 处的系数乘上 \(3!\) 的结果,乘回来的结果肯定是整数,但运算过程中并不是。

考虑怎么绕开求逆。

对于喜欢组合意义的人:

假设我们对集合幂级数 \(F\) 做子集卷积 exp。

考虑第 \(n\) 个元素选或不选。如果不选,那么我们递归求出只考虑前 \(n-1\) 个元素的子集卷积 exp 的结果,记做 \(G_0\)

如果选,那么考虑最终选出包含 \(n\) 的集合是哪个,设其为 \(S\),那么我们就是从 \(F\) 中选出一个包含 \(n\) 这个元素的集合 \(S\),再从 \(G_0\) 中选出一个集合 \(T\),贡献到 \(S\cup T\)

这就是一个简单的子集卷积,可以 \(O(2^nn^2)\) 完成。

而总时间复杂度由递推式 \(T(n)=T(n-1)+O(2^nn^2)\) 计算,可以得到就是 \(O(2^nn^2)\) 的,和原来的复杂度一样,但是好写得多,因为不需要预处理逆元以及推一遍 n^2 求 exp 的式子。

对于喜欢代数推导的人:

原 CF Blog

把集合幂级数视为一个 \(n\) 元截断多项式 \(R(x_1,x_2,\dots,x_n)/(x_1^2,x_2^2,\dots,x_n^2)\)

那么 \(G=\exp(F)\)\([x_n^0]G=\exp([x_n^0]F)\)\([x_n^1]G=[x_n^0]G[x_n^1]\exp(F)=[x_n^0]G[x_n^1]F\)

所以我们只用递归求解 \([x_n^0]G\),就可以通过一次子集卷积求出 \([x_n^1]G\),两者拼接得到 \(G\)

总时间复杂度由递推式 \(T(n)=T(n-1)+O(2^nn^2)\) 计算,可以得到就是 \(O(2^nn^2)\) 的,和原来的复杂度一样,但是好写得多,因为不需要预处理逆元以及推一遍 n^2 求 exp 的式子。

代码实现

这里是两种代码实现测速的地址

不用 vector 的话,简单来说,就一行:

G[0]=1; For(i,0,n-1) Conv(G,F+(1<<i),G+(1<<i),i);

其中 Conv(a,b,c,k) 表示对数组 a 和数组 b 做长为 k 的子集卷积,把结果放到 c 处。

posted @ 2024-07-02 15:30  CharlieVinnie  阅读(32)  评论(0编辑  收藏  举报