如何在不能求逆的时候做子集卷积 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 的式子。
对于喜欢代数推导的人:
把集合幂级数视为一个 \(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
处。