[学习笔记] 子集卷积相关
UltiMadow
2021-08-01 20:43:02
+ 高维前缀和 & sosdp
对于二维前缀和,我们一般有两种计算方法
第一种可以使用容斥,$s_{i,j}=s_{i-1,j}+s_{i,j-1}-s_{i-1,j-1}+a_{i,j}$
第二种,我们可以先选一个维度进行前缀和:$s_{i,j}=s_{i,j-1}+a_{i,j}$,接下来,再对第二个维度进行前缀和:$s_{i,j}\leftarrow s_{i-1,j}+s_{i,j}$
但对于更高维的情况,容斥就不适用了,但是可以用第二种方法,对每个维度依次做前缀和
用高维前缀和的思想,我们可以解决子集 dp 的问题
设 $f_{s}=\sum\limits_{t\subseteq s}a_{t}$,则我们可以看做对 $s$ 做了 $n$ 维前缀和,便可以在 $\mathcal O(n2^n)$ 的时间内解决这个问题
code:
```cpp
for(int i=1;i<=n;i++)
for(int s=0;s<(1<<n);s++)
if(s&(1<<(i-1)))
f[s]=(f[s]+f[s^(1<<(i-1))])%p;
```
+ 快速莫比乌斯变换(FMT)
FMT 可以解决 $\mathrm{or}$ 卷积和 $\mathrm{and}$ 卷积
以 $\mathrm{or}$ 卷积为例,设 $c_i=\sum\limits_{j\mathrm{or}k=i}a_jb_k$
我们对 $a_i$ 做一遍 $n$ 维前缀和,$f_i=\sum\limits_{j\subseteq i}a_j$,得到 $a$ 的 FMT,同样对 $b_i$ 做一遍 $n$ 维前缀和,得到 $b$ 的 FMT $g_i$
接下来把 $f$ 和 $g$ 点乘起来,$h_i=f_ig_i$
考虑 $h$ 和 $c$ 的关系:$h_i=f_ig_i=(\sum\limits_{j\subseteq i}a_j)(\sum\limits_{k\subseteq i}b_k)=\sum\limits_{j,k\subseteq i}a_jb_k=\sum\limits_{j\mathrm{or}k\subseteq i}a_jb_k$,这便是 $c_i$ 的 $n$ 维前缀和
最后把 $h$ 做一遍 $n$ 维差分就可以得到 $c$
时间复杂度 $\mathcal O(n2^n)$
对于 $\mathrm{and}$ 卷积,我们只要把 $n$ 维前缀和换成 $n$ 维后缀和即可
+ 快速沃尔什变换(FWT)
FWT 除了可以解决 $\mathrm{or}$ 卷积和 $\mathrm{and}$ 卷积之外,还可以解决 $\mathrm{xor}$ 卷积
考虑是否存在一种类似 FFT 的变换,能把卷积变成点积
对于 $\mathrm{or}$,考虑一种变换:
$$
\mathrm{FWT}(A)=\begin{cases}
A&(n=0)\\
\mathrm{FWT}(A_0),\mathrm{FWT}(A_1+A_0)&(n>0)
\end{cases}
$$
其中,$A_0$ 表示第 $n$ 位为 $0$ 的序列,$A_1$ 表示第 $n$ 位为 $1$ 的序列,$+$ 为两个序列按顺序分别相加
这样,我们就得到了 $a$ 的 FWT $f$,考虑 $f$ 和 $a$ 的关系
发现序列任何一位如果是 1 都加上了那一位是 0 的值,也就是我们得到了 $f_i=\sum\limits_{j\subseteq i}a_j$,和 FMT 一样
于是我们把 $a$ 的 FWT $f$,$b$ 的 FWT $g$ 点乘起来,得到的就是 $c$ 的 FWT $h$
使用其逆变换:
$$
\mathrm{IFWT}(A)=\begin{cases}
A&(n=0)\\
\mathrm{IFWT}(A_0),\mathrm{IFWT}(A_1-A_0)&(n>0)
\end{cases}
$$
就可以得到 $c$ 了
时间复杂度 $\mathcal O(n2^n)$
code:
```cpp
void FWT(int *A,int sgn){
for(int mid=1;mid<(1<<n);mid<<=1)
for(int j=0,R=(mid<<1);j<(1<<n);j+=R)
for(int k=0;k<mid;k++){
if(sgn==1)A[j+k+mid]=A[j+k+mid]+A[j+k];
else A[j+k+mid]=A[j+k+mid]-A[j+k];
}
}
```
同样的,对于 $\mathrm{and}$ 卷积,也有 FWT:
$$
\mathrm{FWT}(A)=\begin{cases}
A&(n=0)\\
\mathrm{FWT}(A_0+A_1),\mathrm{FWT}(A_0)&(n>0)
\end{cases}
$$
逆变换:
$$
\mathrm{IFWT}(A)=\begin{cases}
A&(n=0)\\
\mathrm{IFWT}(A_0-A_1),\mathrm{IFWT}(A_1)&(n>0)
\end{cases}
$$
code:
```cpp
void FWT(int *A,int sgn){
for(int mid=1;mid<(1<<n);mid<<=1)
for(int j=0,R=(mid<<1);j<(1<<n);j+=R)
for(int k=0;k<mid;k++){
if(sgn==1)A[j+k]=A[j+k]+A[j+k+mid];
else A[j+k]=A[j+k]-A[j+k+mid];
}
}
```
此外,FWT 还可以做 $\mathrm{xor}$ 卷积
在 xor-FWT 之前,先有一个结论
记 $a\circ b$ 为 $a\ \mathrm{and}\ b$ 的二进制表示中 1 的位数(对 2 取模)
有性质 $(a\circ x)\ \mathrm{xor}\ (a\circ y)=a\circ(x\ \mathrm{xor}\ y)$
证明如下:
记 $f(x)$ 表示 $x$ 的二进制表示中 1 的位数
原式等价于 $f(a\ \mathrm{and}\ x)+f(a\ \mathrm{and}\ y)\equiv f(a\ \mathrm{and}\ (x\ \mathrm{xor}\ y))\pmod 2$
接下来,对 $a$,$x$,$y$ 的每一位大力分类讨论即可得证
构造一个变换:$f_i=\sum\limits_{i\circ j=0}a_{j}-\sum\limits_{i\circ j=1}a_j$
记 $a$ 的变换为 $f$,$b$ 的变换为 $g$,$c$ 的变换为 $h$
$$
\begin{aligned}
f_ig_i&=(\sum\limits_{i\circ j=0}a_{j}-\sum\limits_{i\circ j=1}a_j)(\sum\limits_{i\circ k=0}b_{k}-\sum\limits_{i\circ k=1}b_k)\\
&=(\sum\limits_{i\circ j=0}a_{j}\sum\limits_{i\circ k=0}b_{k}+\sum\limits_{i\circ j=1}a_j\sum\limits_{i\circ k=1}b_k)-(\sum\limits_{i\circ j=0}a_{j}\sum\limits_{i\circ k=1}b_k+\sum\limits_{i\circ j=1}a_j\sum\limits_{i\circ k=0}b_{k})\\
&=\sum\limits_{i\circ (j\mathrm{xor}k)=0}a_{j}b_{k}-\sum\limits_{i\circ (j\mathrm{xor}k)=1}a_{j}b_{k}\\
&=h_i
\end{aligned}
$$
(第 3 行用到了上面那个性质)
考虑变换:
$$
\mathrm{FWT}(A)=\begin{cases}
A&(n=0)\\
\mathrm{FWT}(A_0+A_1),\mathrm{FWT}(A_0-A_1)&(n>0)
\end{cases}
$$
考虑最开始说的 $a$ 的变换 $f$
若 $f_x$ 中 $x$ 的第 $i$ 位为 0,则无论 $a_y$ 中 $y$ 的第 $i$ 位为 0 还是 1,$x\circ y$ 都不会改变,所以前面一部分的 $a$ 的 FWT 和 $f$ 是一致的
若 $f_x$ 中 $x$ 的第 $i$ 位为 1,则 $a_y$ 中 $y$ 的第 $i$ 位为 1 时 $x\circ y$ 不会改变,而为 0 时会改变,于是需要把为 0 的部分减掉,而上面式子中 $x\circ y$ 为 1 的系数为 $-1$,所以后面一部分 $a$ 的 FWT 和 $f$ 是一致的
于是得到 $a$ 和 $b$ 的 FWT $f$,$g$ 之后就可以直接点乘得到 $h$ 了
逆变换:
$$
\mathrm{IFWT}(A)=\begin{cases}
A&(n=0)\\
\mathrm{IFWT}(\frac{A_0+A_1}2),\mathrm{IFWT}(\frac{A_0-A_1}2)&(n>0)
\end{cases}
$$
code:
```cpp
void FWT(int *A,int sgn){
for(int mid=1;mid<(1<<n);mid<<=1)
for(int j=0,R=(mid<<1);j<(1<<n);j+=R)
for(int k=0;k<mid;k++){
int x=A[j+k],y=A[j+k+mid];
if(sgn==1)A[j+k]=x+y,A[j+k+mid]=x-y;
else A[j+k]=(x+y)/2,A[j+k+mid]=(x-y)/2;
}
}
```
+ 子集卷积
考虑这个式子:$c_i=\sum\limits_{j\mathrm{or}k=i,j\mathrm{and}k=\emptyset}a_jb_k$
直接枚举子集可以做到 $\mathcal O(3^n)$,不够优秀
如果没有 $j\ \mathrm{and}\ k=\emptyset$ 的限制,则直接 $\mathrm{or}$ 卷积即可完成
考虑转换一下限制
设 $f(x)$ 为 $x$ 的二进制表示中 1 的位数
则原限制等价于 $j\ \mathrm{or}\ k=i$ 且 $f(j)+f(k)=f(j\ \mathrm{or}\ k)$
于是将 $a$ 数组根据 $f(i)$ 拆开,$a'_{i,s}$ 仅当 $f(s)=i$ 时等于 $a_s$
同样将 $b$ 数组拆开成 $b'$,$c$ 数组拆开成 $c'$
则考虑进行如下卷积:$c'_{i,s}=\sum\limits_{j+k=i,p\mathrm{or}q=s} a'_{j,p}b'_{k,q}$,得到的 $c'$ 中 $f(s)=i$ 的即为答案
于是对于所有的 $a'$,$b'$ 都做一遍 or-FWT,之后做卷积得到 $c'$ 的 FWT,再都做一遍 or-IFWT,得到 $c'$,就可以将答案提取出来了
时间复杂度 $\mathcal O(n^22^n)$
code:
```cpp
for(int i=0;i<(1<<n);i++)scanf("%d",&a[pcnt[i]][i]);
for(int i=0;i<(1<<n);i++)scanf("%d",&b[pcnt[i]][i]);
for(int i=0;i<=n;i++)FWT(a[i],1),FWT(b[i],1);
for(int i=0;i<=n;i++)
for(int j=0;j<(1<<n);j++)
for(int k=0;k<=i;k++)
c[i][j]=(c[i][j]+1ll*a[k][j]*b[i-k][j]%p)%p;
for(int i=0;i<=n;i++)FWT(c[i],-1);
for(int i=0;i<(1<<n);i++)printf("%d ",c[pcnt[i]][i]);
//pcnt[i]为f(i)
```
+ 例题
[P4717 【模板】快速莫比乌斯/沃尔什变换 (FMT/FWT)](https://www.luogu.com.cn/problem/P4717)
[P6097 【模板】子集卷积](https://www.luogu.com.cn/problem/P6097)
↑ 板子题 ↑
[CF1034E Little C Loves 3 III](https://www.luogu.com.cn/problem/CF1034E)
> 题意:在模 4 意义下求子集卷积
$n\le 21$
直接子集卷积是 $\mathcal O(n^22^n)$ 的,不能通过此题,所以需要进行一些转化
记 $f(x)$ 为 $x$ 的二进制表示中 1 的位数
考虑设 $f_i=a_i\times 4^{f(i)},g_i=b_i\times 4^{f(i)}$
接下来直接对 $f$ 和 $g$ 做 $\mathrm{or}$ 卷积,得到 $h_i=\sum\limits_{j\mathrm{or}k}a_jb_k\times 4^{f(j)+f(k)}$
接下来把 $h_i$ 除以 $4^{f(i)}$ 并对 4 取模,就把 $f(j)+f(k)\not =f(i)$ 的不合法情况去掉了(除以 $4^{f(i)}$ 去掉了 $f(j)+f(k)<f(i)$ 的情况,对 4 取模去掉了 $f(j)+f(k)>f(i)$ 的情况,而 $f(j)+f(k)=f(i)$ 的情况正好留下来了并且对 4 取了模)
那么时间复杂度 $\mathcal O(n2^n)$
[CF1326F2 Wise Men (Hard Version)](https://www.luogu.com.cn/problem/CF1326F2)
> 题意:给定一个 $n$ 个点的无向图,一个 $n$ 个点的排列 $p$ 能生成的 01 串的第 $i$ 位为 $p_i$ 和 $p_{i+1}$ 的连边情况,有边为 0,无边为 1
对于每个 01 串,求有多少种排列可以生成它
$2\le n\le 18$
考虑对于每个 01 串对排列的限制:第 $i$ 位为 0 要求必须无边,为 1 要求必须有边
显然有边的限制比无边的要好处理一点,于是可以考虑去掉无边的限制
去掉必须无边的限制之后,我们发现答案其实求解的是一个集合 $s$ 的高维后缀和,于是我们得到去掉限制的答案之后直接 and-IFWT/and-IFMT 就可以得到原来的答案了
如果只考虑必须有边,原题就被转化成了原图拆成若干条点不相交的链,对每个 01 串的贡献
发现这就是一个整数拆分问题,可以暴力 dfs 求解
记 $f(x)$ 为 $x$ 的二进制表示中 1 的个数
设 $f_{s}$ 为 $s$ 集合中的点组成一条链的方案数,可以用 dp 在 $\mathcal O(n^22^n)$ 的时间内预处理出来
在整数拆分时,我们维护 $g_s$ 为 $s$ 集合中的点组成若干条链,链的长度为当前 dfs 的整数拆分的方案数
我们发现 $g$ 在每次转移的时候要和 $f$ 做一次子集卷积,很慢
我们考虑在预处理时把 $f_s$ 拆成 $f_{f(s),s}$,然后预先对 $f_{i}$ 跑一遍 or-FWT,这样每次转移 $g$ 的时候只需要把 $f_i$ 点乘进去就行了(这里 $i$ 表示当前 dfs 的整数拆分)
这样计算出的 $g$ 其实是 $g$ 的 or-FWT,所以需要进行一次 or-IFWT,但是这样还是太慢了
发现计算时只需要全集的答案 $g_U$,而 or-FWT 其实是做了高维前缀和,所以对 $g_U$ 做一次高维差分即可
这里算出的 $g$ 是对于每个整数拆分的答案,我们可以把它用 hashmap 建立一个整数拆分到对应答案的映射
最后对于每个 01 串,都有一个对应的整数拆分序列,于是直接在 hashmap 里查询出答案即可
最后别忘了做一遍 and-IFWT
时间复杂度 $\mathcal O(\sum\limits_{i\in p(n)}g(i)\times 2^n)$
其中 $p(n)$ 为 $n$ 的整数拆分集合,$g(i)$ 为当前整数拆分方案拆出来数的个数