快速沃尔什变换&&ABC396G

FWT

快速沃尔什变换(FWT)是解决位运算卷积问题,即给定序列 \(a,b\),求序列 \(c\) 满足\(c_i=\sum_{i=j\oplus k}A_j\times B_k\),其中 \(\oplus\) 为一种位运算符号。

因为本文是讲题的前置,所以这里只讲异或卷积,也是最难的一个。

沃尔什变换其核心思想是先对序列 \(a,b\) 做一遍正变换,假设对其做正变换后得到的序列为 \(A\)\(B\),然后对两个得到的新序列进行一定操作得到 \(C\),然后对 \(C\) 做一遍逆变换即可。

规定 \(popcnt(x)\)\(x\) 二进制下 \(1\) 的个数,\(x\circ y=popcnt(x \ \mathrm{and }\ y)\bmod 2\)\(x\)\(y\) 按位与后二进制中 \(1\) 个数的奇偶性。

存在分配律 \((x\circ y)\oplus (x\circ z)=x\circ(y\oplus z)\)

接下来是一个构造,我们定义 \(A_i=\sum_{i\circ j=0}a_j-\sum_{i\circ j=1} a_j,B_i=\sum_{i\circ j=0}b_j-\sum_{i\circ j=1} b_j,C_i=\sum_{i\circ j=0}c_j-\sum_{i\circ j=1} c_j\)

考虑证明 \(C_i=A_i\times B_i\),笔者数学水平讲不出来,直接看一下吧[1]

\[\begin{align}A_i\times B_i&=(\sum_{i\circ j=0}a_j-\sum_{i\circ j=1} a_j)(\sum_{i\circ k=0}b_k-\sum_{i\circ k=1} b_k)\\ &=(\sum_{i\circ j=0}a_j\sum_{i\circ k=0}b_k+\sum_{i\circ j=1} a_j\sum_{i\circ k=1} b_k)-(\sum_{i\circ j=0}a_j\sum_{i\circ k=1} b_k+\sum_{i\circ j=1} a_j\sum_{i\circ k=0}b_k)\\ &=\sum_{i\circ (j\oplus k)=0}a_j b_k -\sum_{i\circ (j\oplus k)=1}a_j b_k\\ &=C_i \end{align} \]

如何计算?

\(\mathrm{merge}(a,b)\) 表示两个序列拼接组成的序列,定义序列的加法 \(a+b\) 为对应点值相加。

对于序列 \(A\) 有 $A=\mathrm{merge}(A_0+A_1,A_0-A_1) $,不会证明。

逆变换为 \(a=\mathrm{merge}(\frac{a_0+a_1}{2},\frac{a_0-a_1}{2})\),这里异或卷积知道正变换后很容易推出逆变换。

代码实现类似FFT,不用递归用迭代。

inline void FWT(int *P,int type){
  for(int i=1;i<n;i<<=1){
      int p=i<<1;
      for(int j=0;j<n;j+=p){
          for(int k=0;k<i;k++){
              FWT[j+k]=FWT[j+k]+FWT[i+j+k];
              FWT[i+j+k]=FWT[j+k]-FWT[i+j+k];
              if(type==-1){
              	  FWT[j+k]>>=1;
              	  FWT[i+j+k]>>=1;
              }
          }
      }
  }
  return;
}
FWT(a,1);
FWT(b,1);
for(int i=1;i<=n;i++)a[i]*=b[i];
FWT(a,-1);

ABC396G

题意:有一个 \(h\)\(w\) 列的表格,每个元素都是 \(0/1\) ,每次操作可以选择一行或一列,把 \(0/1\) 翻转,即把 \(0\) 换为 \(1\) ,把 \(1\) 换为 \(0\) 。请问经过若干次操作后,表格中最少有多少个 \(1\)

注意到 \(h\) 很大 \(w\) 很小,我们对行状压。

当列的翻转状态确定的时候,答案是唯一的。

设列的翻转状态为 \(x\),每行的状态为 \(y_i\),对 \(x\) 进行枚举,显然第 \(i\) 行翻转完的状态为 \(y_i\oplus x\),记作 \(s_i\)

设一个状态中 \(0\) 的个数和 \(1\) 的个数中较小的值为 \(f\),即 \(f_s=\min{(cnt_0(s),cnt_1(s))}\)

暴力做法为 \(ans=\min\sum_{i=1}^{h}f_{s_i}\),时间复杂度为 \(O(h\times 2^w)\),T飞。

\(ans_X=\sum_{i=1}^{h}f_{s_i}=\sum_{i=1}^{h}f_{y_i\oplus X}\)

设所有行中有 \(g_s\) 行状态为 \(s\),上式

\[\begin{align} &\Leftrightarrow ans_X=\sum_{i=0}^{2^w-1}f_{i\oplus x}\times g_{i}\\ &\Leftrightarrow ans_X=\sum_{i=0}^{2^w-1}\sum_{j=0}^{2^w-1}[j\oplus X=i]f_{i}\times g_{j}\\ &\Leftrightarrow ans_X=\sum_{i=0}^{2^w-1}\sum_{j=0}^{2^w-1}[i\oplus j=X]f_{i}\times g_{j}\\ &\Leftrightarrow ans_X=\sum_{i\oplus j=X}f_i\times g_j \end{align} \]

FWT秒了。

预处理 \(f,g\) 的代码如下

for(int i=0;i<(1<<w);++i)
	f[i]=f[i>>1]+(i&1);
for(int i=0;i<(1<<w);++i)
	f[i]=min(f[i],w-f[i]);
for(int j=1;j<=h;j++){
	long long tmp=0;
	for(int i=1;i<=w;i++)
		tmp+=(a[i][j]<<(i-1));
	g[tmp]++;
}

\(2^w\)\(N\),时间复杂度为 \(O(N \log N)\)


  1. https://oiwiki.org/math/poly/fwt/#异或运算 ↩︎

posted @ 2025-03-14 23:13  FinderHT  阅读(22)  评论(1)    收藏  举报