Loading

FWT 快速沃尔什变换

参考文献


前置知识

  • \(\text{FFT}\) 快速傅里叶变换

  • 二进制位运算

问题引入

给出 \(n\) 和两个长为 \(2^n\) 的序列 \(a_{0...2^n-1},b_{0...2^n-1}\),求一个序列 \(c\),对于 \(i(1\le i<2^n)\)

\[c_i=\sum_{j\oplus k}a_jb_k \]

其中符号 \(\oplus\) 是三个位运算中的一个,即 \(\text{or,and,xor}\)。数据范围:\(1\le n\le20\)


显然,这是一个卷积的问题,但符号不一样,可能是 \(\text{or,and,xor}\) 三种,我们称之为位运算卷积

思路转化

考虑 \(\text{FFT}\) 是如何快速计算两个序列的加卷积的,我们把系数序列转成点值的形式,然后每一位分别相乘,最后插值回来。

我们尝试利用这种思想,设 \(fwt_a[i]\) 表示 \(a\) 序列经过变换后的第 \(i\) 位的值。

为了满足条件,我们构造的 \(fwt\) 序列需要满足:

\[c=a*b \Leftrightarrow fwt_c=fwt_a\cdot fwt_b \]

其中 \(*\) 表示卷积,\(\cdot\) 表示点积。

\(\text{or}\) 卷积


\(c_i=\sum_{j\text{or} k}\)

不妨设 \(fwt_a[i]=\sum\limits_{j|i=i}a_j\)。若 \(fwt_c[i]=fwt_a[i]\cdot fwt_b[i]\),尝试验证是否满足卷积式子。

\[\begin{aligned} fwt_a[i]\cdot fwt_b[i] &= (\sum_{j|i=i}a_j)\cdot (\sum_{k|i=i}b_k) \\ &= \sum_{j|i=i}\sum_{k|i=i} a_jb_k \\ &= \sum_{(j|k)|i=i} a_jb_k \\ &= \sum_{d|i=i}\sum_{j|k=d} a_jb_k \\ &= \sum_{d|i=i}c_d \\ &= fwt_c[i] \end{aligned} \]

发现满足,我们只需要求出 \(a,b\) 序列的 \(fwt_a,fwt_b\) 就能求出 \(fwt_c\)

考虑分治,按最高位分治。

\(a_0\)\(a\)下标最高位为 \(0\) 的数组成的序列(换句话说,就是 \(a\) 的前半部分),\(a_1\)\(a\)下标最高位为 \(1\) 的数组成的序列(就是 \(a\) 的后半部分)。如果我们求出了 \(fwt_{a_0},fwt_{a_1}\),我们有

\[fwt_a=\text{merge}(fwt_{a_0},fwt_{a_0}+fwt_{a_1}) \]

其中 \(+\) 表示相应位置相加,\(\text{merge}\) 表示两个序列拼接。

这个式子的意思就是把下标最高位为 \(0\) 的数贡献到为 \(1\) 的数。具体实现时,我们采用递推的方式,从地位往高位递推。

void fwt_or(ll *a,ll n)
{
	for(ll i=1;i<n;i<<=1)
	{
		for(ll j=0;j<n;j+=(i<<1))
		{
			for(ll k=0;k<i;k++)
			{
				a[i+j+k]+=a[j+k];
			}
		}
	}
}

现在我们求出了 \(fwt_a,fwt_b\),进而求出了 \(fwt_c(fwt_c=fwt_a\cdot fwt_b)\),考虑如何进行 \(\text{IFWT}\),把 \(fwt_c\) 转成 \(c\)

考虑从高位到地位递推,把式子反过来得:\(fwt_{a_0}=fwt_0,\space fwt_{a_1}=fwt_1-fwt_0\)。其中 \(fwt_0\) 表示 \(fwt_a\) 的前半部分,\(fwt_1\) 同理。

但这样做要求我们从大到小递推,发现其实我们递推的顺序没有很大关系,反正变换都是在子集上做的,从高到低和从低到高没有区别。

void ifwt_or(ll *a,ll n)
{
	for(ll i=1;i<n;i<<=1)
	{
		for(ll j=0;j<n;j+=(i<<1))
		{
			for(ll k=0;k<i;k++)
			{
				a[i+j+k]-=a[j+k];
			}
		}
	}
}

把两个函数结合一下,可得

void fwt_or(ll *a,ll n,ll v)
{
    // v=1或-1
	for(ll i=1;i<n;i<<=1)
	{
		for(ll j=0;j<n;j+=(i<<1))
		{
			for(ll k=0;k<i;k++)
			{
				a[i+j+k]+=a[j+k]*v;
			}
		}
	}
}

\(\text{and}\) 卷积


类似于 \(\text{or}\) 卷积,我们可以得出

\[fwt_a[i]=\sum_{j\&i=i}a_j \]

\[fwt_a=\text{merge}(fwt_{a_0}+fwt_{a_1},fwt_{a_1}) \]

\[a=\text{merge}(a_0-a_1,a_1) \]

void fwt_and(ll *a,ll n,ll v)
{
	for(ll i=1;i<n;i<<=1)
	{
		for(ll j=0;j<n;j+=(i<<1))
		{
			for(ll k=0;k<i;k++)
			{
				a[j+k]+=a[i+j+k]*v;
			}
		}
	}
}

\(\text{FWT}\) 线性变换矩阵的本质


考虑从线性变换矩阵入手,设矩阵为 \(w(i,j)\),即 \(fwt_a[i]=\sum\limits_jw(i,j)a_j\)

因为

\[fwt_c[i]=\sum_jw(i,j)c_j \]

所以

\[fwt_c[i]=\sum_jw(i,j)\sum_{x\oplus y=j}a_xb_y=\sum_x\sum_yw(i,x\oplus y)a_xb_y \]

又知 \(fwt_c[i]=fwt_a[i]\cdot fwt_b[i]\),进而得到

\[fwt_c[i]=(\sum_jw(i,x)a_x)\cdot(\sum_kw(i,y)b_y)=\sum_x\sum_yw(i,x)w(i,y)a_xb_y \]

比较上下两个式子,得到 \(w(i,x)w(i,y)=w(i,x\oplus y)\),只要变换矩阵满足这个式子即可。

由于位运算每一位都是独立的,我们只需要考虑 \(w(0/1,0/1)\) 就好。

对于 IDWT,我们其实就是乘个矩阵逆,所以我们构造出来的矩阵必须满足可逆。

\(\text{xor}\) 卷积


对于 \(i,x,y\),根据 \(\text{FWT}\) 变换矩阵的性质,有 \(w(i,x)w(i,y)=w(i,x \space\text{xor} \space y)\)

对于 \(w(0,0)\)\(w(0,0)w(i,j)=w(i,j)\),所以 \(w(0,0)=1\)

对于 \(w(1,1)\)\(w(1,1)w(1,1)=w(1,0)\)。如果 \(w(1,1)=w(1,0)=0\) 那么一行全是 \(0\),没有逆,所以 \(w(1,1)=\pm 1\),而 \(w(1,0)=1\)

对于 \(w(0,1)\)\(w(0,1)w(0,1)=w(0,0)=1\),所以 \(w(0,1)=\pm 1\)

不妨令 \(w(1,1)=-1,w(0,1)=1\)

不难发现,\((i,j)\) 的变换系数就是 \((-1)^{\text{popcount}(i\&j)}\)


尝试分治构造 \(fwt_a\),仍然是按最高位 \(0,1\) 构造,分成 \(a_0,a_1\),假设已经构造出了 \(fwt_{a_0},fwt_{a_1}\)

对于 \(i(i<2^{n-1})\)(即 \(i\)最高位为 \(0\)),以及 \(j(j\ge 2^{n-1})\),由于 \(i\operatorname{and}j\) 的最高位是 \(0\),因此 \(j\) 的最高位对 \(i\) 没有贡献。所以我们对于下标最高位为 \(0\)\(fwt_a[i]\),把 \(fwt_{a_0},fwt_{a_1}\) 视为同类,即 \(fwt_a\) 的左半边为 \(fwt_{a_0}+fwt_{a_1}\)

对于 \(i(i\ge 2^{n-1})\),若 \(j\) 最高位为 \(1\) 就有贡献,\(0\) 则没贡献,那么 \(fwt_a\) 的右半边为 \(fwt_{a_0}-fwt_{a_1}\)

所以

\[fwt_a=\text{merge}(fwt_{a_0}+fwt_{a_1},fwt_{a_0}-fwt_{a_1}) \]

void fwt_xor(ll *a,ll n)
{
	for(ll i=1;i<n;i<<=1)
	{
		for(ll j=0;j<n;j+=(i<<1))
		{
			for(ll k=0;k<i;k++)
			{
				a[j+k]+=a[i+j+k];
				a[i+j+k]=a[j+k]-2*a[i+j+k];
			}
		}
	}
}

考虑如何进行 \(\text{IFWT}\),式子可以从上面的推导。

直接反过来

\[fwt_{a_0}=\frac{fwt_0+fwt_1}2,\space fwt_{a_1}=\frac {fwt_0-fwt_1}2 \]

同理,从高到低和从低到高没有区别,可以从低到高递推。

void ifwt_xor(ll *a,ll n)
{
	for(ll i=1;i<n;i<<=1)
	{
		for(ll j=0;j<n;j+=(i<<1))
		{
			for(ll k=0;k<i;k++)
			{
				a[j+k]+=a[i+j+k];
				a[i+j+k]=a[j+k]-2*a[i+j+k];
                a[j+k]>>=1; a[i+j+k]>>=1;
			}
		}
	}
}

合并成一个函数

void fwt_xor(ll *a,ll n,ll flag)
{
	for(ll i=1;i<n;i<<=1)
	{
		for(ll j=0;j<n;j+=(i<<1))
		{
			for(ll k=0;k<i;k++)
			{
				a[j+k]+=a[i+j+k];
				a[i+j+k]=a[j+k]-2*a[i+j+k];
                if(flag) a[j+k]>>=1, a[i+j+k]>>=1;
			}
		}
	}
}

\(k\) 进制 \(\text{FWT}\)

承接 \(k\) 进制 FWT 小记

例题


发现 \(n\) 很小,考虑直接枚举操作哪些行。

枚举操作的行的集合为 \(S\)。对于每一列,记 \(msk[i]\) 表示第 \(i\)\(n\) 个数的 \(0/1\) 状态,那么操作之后的状态为 \(S\operatorname{xor} msk[i]\)。此时,我们可以选择操作第 \(i\) 列或不操作,这取决于 \(msk[i]\) 在二进制下 \(1\) 的个数 \(\text{popcount}(msk[i])\)

  • \(\text{popcount}(msk[i])\le \frac {n}2\) 时,不操作。

  • \(\text{popcount}(msk[i])>\frac n2\) 时,操作。

所以我们的任务既是统计 \(\text{popcount}(msk[i])\le\frac n2\)\(i\) 的个数。

枚举的 \(S\) 是不断改变的,我们不能动态得知 \(\text{popcount}\) 的计数。设 \(msk'[i]\) 为操作后的 \(msk[i]\),因为 \(msk[i]\operatorname{xor}S=msk'[i]\),可得 \(S=msk[i]\operatorname{xor}msk'[i]\)

\(cnt[j]\) 表示有多少个 \(i\) 满足 \(msk[i]=j\),设 \(h[k,S]\) 表示有多少个 \(i\) 满足操作后 \(\text{popcount}(msk'[i])=k\)

那么

\[h[k,S]=\sum_{x\operatorname{xor}y=S} cnt[x]\cdot[\text{popcount}(y)=k] \]

这是一个异或卷积的形式,直接 \(\text{FWT}\) 即可。

我们可以预处理出所有集合 \(S\),判断其是否合法。

不难想到状压 \(\text{DP}\):设 \(f[S]\) 表示集合 \(S\) 的答案。

枚举 \(T\in S,\space T\not=S\),表示划分的第一个州区。转移:

\[f[S]=\sum\limits_{T\in S,T\not=S} [T\text{合法}]\cdot f[S-T]\cdot (\frac{sum[T]}{sum[S]})^p=(\frac 1{sum[S]})^p\sum\limits_{T\in S,T\not=S} [T\text{合法}]\cdot f[S-T]\cdot sum[T]^p \]

其中 \(sum[S]\) 表示一个州区的人数和。

注意到子集卷积有个很经典的东西:

  • \(T_0\) 表示 \(S-T\)。把 \(T\in S\) 的条件转化成枚举 \(T,T_0\),满足 \(T|T_0=S,\space T\&T_0=0\),即

\[f[S]=(\frac 1{sum[S]})^p\sum_{T|T_0=S,T\&T_0=0} [T\text{合法}]\cdot f[S-T]\cdot sum[T]^p \]

考虑把 \(T\&T_0\) 这个限制去掉,不难发现

  • \(\text{popcount}(T)+\text{popcount}(T_0)=\text{popcount}(T|T_0)\) 时,\(T\&T_0=0\)

\(val[i,S]=[\text{popcount}(S)=i]\cdot sum[S]\cdot [S\text{合法}]\)\(F[i,S]=[\text{popcount}(S)=i]\cdot f[S]\)

可得

\[F[i,S]=\sum_{j=1}^i\sum_{T|T_0=S} val[j,T]\cdot F[i-j,T_0] \]

直接 \(\text{FWT}\) 即可,时间复杂度 \(O(n^22^n)\)

观察到 \(n\) 特殊的数据范围,考虑 \(\text{meet in middle}\)

先转化,“选若干个点,两端至少有一个点被选的边的条数为偶数”相当于选择这些点的补集,满足导出子图边数与 \(m\) 奇偶性相同。

\(c=m\mod 2\)。把 \(n\) 个点分成前 \(k\) 个和后 \(n-k\) 个,那么选的点的导出子图包含的边有以下几种:

  • 一类边:两端都属于前 \(k\) 个点

  • 二类边:两端都属于后 \(n-k\) 个点。

  • 三类边:一端属于前 \(k\) 个点,一端属于后 \(n-k\) 个点。

\(d_1[S]\) 表示选了前 \(k\) 个点的集合 \(S\),导出子图的一类边数量,\(d_2[S]\) 则为后 \(n-k\) 个点的集合 \(S\),导出子图的二类边数量,设 \(a[S]\) 表示选了前 \(k\) 个点的集合 \(S\),与后 \(n-k\) 个点的贡献集合。

不难写出答案式子:

\[\sum_{S_1}\sum_{S_2}[(d_1[S_1]+d_2[S_2]+\text{popcount}(a[S_1]\&S_2))\bmod 2=c] \]

里面有个 \(\text{popcount}\) 很烦,考虑枚举 \(\text{popcount}\) 里面的东西:

\[\sum_{T}\sum_{S_0\&S_2=T}cnt[S_0,c\operatorname{xor}(\text{popcount}(T)\bmod 2)\operatorname{xor}(d_2[S_2]\bmod 2)] \]

其中 \(cnt[k,S_0]\) 表示 \(d_1[S_1]\bmod 2=k\) 的且 \(a[S_1]=S_0\)\(S_1\) 的个数。然后又发现 \((d_2[S_2]\bmod 2)\) 很烦,拆出来:

\[\sum_{T}\sum_{S_0\&S_2=T}cnt[c\operatorname{xor}(\text{popcount}(T)\bmod 2),S_0]\cdot [d_2[S_2]\bmod 2=0]+cnt[c\operatorname{xor}(\text{popcount}(T)\bmod 2)\operatorname{xor}1,S_0]\cdot [d_2[S_2]\bmod 2=1] \]

惊奇地发现这是个与卷积的形式,我们只需要分 \(\text{popcount}(T)\bmod 2\) 的取值分开卷积即可,\(\text{FWT}\) 即可做到 \(O((\frac n2)2^{\frac n2})\)

posted @ 2023-08-25 11:42  Lgx_Q  阅读(20)  评论(0编辑  收藏  举报