[数学] 哈集幂 10 元教程(1,不一定有 2)
什么我们集合幂级数十元教程,竟然这么的秀吗?
前情提要。虽然那个题
本文不会介绍但是需要知道的:
- 高维前缀和
- FFT 优化乘法的思想(化为某点值形式、对位相乘、从点值形式化回去)。不需要真的会 FFT,但是建议先去学。
- 基本的多项式和形式幂级数思想(知道
x^n 中的x 不是一个数而是一个下标状物)。不需要会多项式 exp,ln,求逆等等。
鉴于集合幂级数的初学者都知道上面三个东西,本文可以说是相当
::::info 不保证本文的数学用词严谨性。仅保证这是面向初学者的集合幂级数教学,因此措辞会相对随意。 ::::
1. 集合幂级数引入
如果学过背包或者多项式乘法,你就会知道这类问题的本质是
我们考虑魔改 DP 形式。现在假设我在做状压 DP,希望
你看这个自然语言描述系数很不方便啊,不妨令
那么我们就定义了
2. 卷积运算
2.0 定义
上面用
但是要知道乘法本身就有多种意思,比如 FFT 和 FWT 做的事情我们都叫做加速卷积。
这里面具体的区别其实就是“贡献”的条件不同。多项式乘法中“贡献”的条件是
比如我们来看 FWT 模板题题面:
给定长度为
2^n 两个序列A,B ,设C_i=\sum_{j\oplus k = i}A_j \times B_k 分别当
\oplus 是 or, and, xor 时求出C 。
这三种贡献形式就可以分别被叫做或卷积、与卷积、异或卷积。
下面,我将讲解不同种类的卷积应当怎样实现。
2.1 或卷积
FMT,快速莫比乌斯变换,即对于
但是你尚不知道其作用。FMT 就是单纯求一个前缀和吗?
显然并不只有那么简单。我们知道,两个多项式用 DFT 处理一下之后可以做到
FMT 能够加速的卷积是与卷积和或卷积。
它的原理是什么?拿或卷积举例子,其要求
我们发现
与卷积和或卷积没有任何本质区别,只是把
2.2 异或卷积
这部分的代码存在其他理解方式,学不懂可以参考模板题解,换个方向。
FWT,快速沃尔什变换,同样是加速计算用的,它可以加速异或卷积。
考虑异或卷积
我们有
这个艾弗森括号必须拆了,不然
设
证明并不难。
由此消去艾弗森括号得到
这个
于是
一个惊人的事实是,
形式上的,就是:
现在我们需要做的,就是快速进行如下变换:
只要能在合理复杂度内进行这两个操作,那么异或卷积也被解决了。
考虑你有一棵
::::success[三种变换的实现]
// 快速莫比乌斯变换
inline void OR(long long *f, int type)
{
// type = 1 表示高维前缀和,否则表示高维前缀差分
for(int i = 1; i <= n; ++i)
for(int j = 0; j < (1 << n); j += (1 << i))
for(int k = j; k < j + (1 << (i - 1)); ++k)
Plus(f[k + (1 << (i - 1))], mod + f[k] * type);
}
inline void AND(long long *f, int type)
{
// type = 1 表示高维后缀和,否则表示高维后缀差分
for(int i = 1; i <= n; ++i)
for(int j = 0; j < (1 << n); j += (1 << i))
for(int k = j; k < j + (1 << (i - 1)); ++k)
Plus(f[k], mod + f[k + (1 << (i - 1))] * type);
}
// 快速沃尔什变换
inline void XOR(long long *f, int type)
{
long long iv = qpow(qpow(2, n), mod - 2);
for(int i = 1; i <= n; ++i)
{
for(int j = 0; j < (1 << n); j += (1 << i))
{
for(int k = j; k < j + (1 << (i - 1)); ++k)
{
long long n1 = f[k], n2 = f[k + (1 << (i - 1))];
f[k] = (n1 + n2) % mod, f[k + (1 << (i - 1))] = (n1 - n2 + mod) % mod;
}
}
}
if(type == -1)
{
for(int i = 0; i < (1 << n); ++i) f[i] = f[i] * iv % mod;
}
}
::::
上面所谓的另一种理解方式是直接上分治算法讨论,相较于这个推式子是要简单一些。
2.3 子集卷积
回顾或卷积的定义,
回忆做状压 DP 时,很多题目要求的是
现在介绍或卷积的改造,可以有效处理不交这个限制。
首先明确,我们想要的是
这个不交的限制等价于
我们把
我们用
你发现你要的原
2.4 三种变换的其他理解方式
【待补充】
2.5 k 维 FWT 扩展
【待补充】
2.6 半在线卷积
有时哈集幂也会出现自己卷自己的情况,比如我们需要计算
如果这个级数是一般的形式幂级数,我们可以用分治 FFT,即 cdq 分治解决。
但是这是哈集幂,不能分治 FFT 了。我们思考分治 FFT 把在线变成离线的原理,是给卷积的贡献过程划分了顺序。我们也可以尝试给集合幂级数的卷积“确定顺序”。
不妨按照
你将会在 3.2 部分看到半在线卷积的具体实现。
3. 集合幂级数的常用函数
3.1 \exp
什么是
可以看出,
那么其组合意义自然也是相对应的:
有
n 个元素,并给定2^n 种权值。一个集合的美丽度定义为在所有划分方式中划分出的每个集合的权值乘积之和。现在你只知道美丽度,求初始权值。
设权值
则我们可以先枚举
::::info[核心代码]
for(int ID = 1; ID <= n; ++ID)
{
// 枚举 highbit 是哪个元素
int cur = (1 << (ID - 1)); // ID 的二进制编码
// 考虑对 f[S] = g[S] - \sum f[T] * g[S - T] 进行半在线卷积求解
// 先对 f, g 进行 FMT
for(int i = 0; i < ID; ++i) FMT(g[i], ID - 1, 1);
for(int i = 1; i <= ID; ++i) FMT(f[i] + cur, ID - 1, 1);
for(int i = 1; i <= ID; ++i)
{
// 半在线卷积,处理到 i 时 |S| = i 的 f[S] 已经计算完成
FMT(f[i] + cur, ID - 1, -1); // 进行 IFMT 求出贡献真实值
// 计算出 f
for(int j = 0; j < cur; ++j)
f[i][j + cur] = g[i][j + cur] - f[i][j + cur];
FMT(f[i] + cur, ID - 1, 1); // 再进行 FMT 准备后续乘法
for(int j = i + 1; j <= ID; ++j) // 贡献到 j
for(int k = 0; k < cur; ++k)
f[j][k + cur] += f[i][k + cur] * g[j - i][k];
}
for(int i = 0; i < ID; ++i) FMT(g[i], ID - 1, -1);
for(int i = 1; i <= ID; ++i) FMT(f[i] + cur, ID - 1, -1);
}
::::
3.3 逆
逆,即
不妨解方程,
然后考虑
这是因为
我们要求的是
于是套用半在线卷积的算法即可。
4. 小结
现在你可以做的题:
- P4717 【模板】快速莫比乌斯 / 沃尔什变换 (FMT / FWT)
- P6097 【模板】子集卷积
- P12232 集合幂级数求逆
- P13843 集合幂级数 exp(非素数模数)
- P13844 集合幂级数 ln(非素数模数)
后面两题的质数版本没必要做,反正上文已经讲了通用做法了。
下一篇(如果有的话)集合幂级数教程将讲解怎么使用哈集幂解题。
(已褪色.jpeg)