[学习笔记]快速沃尔什变换 (FWT)

FWT的简介

一般 FWT \text{FWT} FWT用来解决一下问题:

  • C k = ∑ i ∣ j = k A i B j C_k=\sum_{i|j=k}A_iB_j Ck=ij=kAiBj
  • C k = ∑ i & j = k A i B j C_k=\sum_{i\&j=k}A_iB_j Ck=i&j=kAiBj
  • C k = ∑ i ⊕ j = k A i B j C_k=\sum_{i\oplus j=k}A_iB_j Ck=ij=kAiBj

实现的大概思路就是就是先把他们转化成 fwt(A) \text{fwt(A)} fwt(A)(类似 FFT \text{FFT} FFT的点值表达),然后对应为相乘,最后在还原为多项式(整个过程很类似与快速傅里叶变换)

主要的就是我们设计的正变换要满足可以直接相乘得到 C C C 的正变换,比如 o r or or 卷积的证明:
C i ′ = ∑ j ∈ i C j = ∑ j ∈ i ∑ a ∣ b = j A a B b C'_i=\sum_{j\in i}C_{j}=\sum_{j\in i}\sum_{a|b=j}A_{a}B_b Ci=jiCj=jiab=jAaBb = ∑ a ∈ i A a ∑ b ∈ i B b = A a ′ B b ′ =\sum_{a\in i}A_a\sum_{b\in i} B_b=A_a'B_b' =aiAabiBb=AaBb

or 卷积

现在要做到这个的快速卷积: C k = ∑ i ∣ j = k A i B j C_k=\sum_{i|j=k}A_iB_j Ck=ij=kAiBj

定义 A ∣ B A|B AB为多项式的 or \text{or} or卷积,显然 A ∣ B = B ∣ A A|B=B|A AB=BA(交换律), ( A + B ) ∣ C = A ∣ C + B ∣ C (A+B)|C=A|C+B|C (A+B)C=AC+BC(结合律)

定义 f w t ( A ) [ k ] = ∑ i ∣ k A i fwt(A)[k]=\sum_{i|k}A_i fwt(A)[k]=ikAi,因为我们要让 f w t ( C ) = f w t ( A ) × f w t ( B ) fwt(C)=fwt(A)\times fwt(B) fwt(C)=fwt(A)×fwt(B),这个基于的原理就是若 i ∣ k = k i|k=k ik=k j ∣ k = k j|k=k jk=k,就能推出 ( i ∣ j ) ∣ k = k (i|j)|k=k (ij)k=k,那么原来是 k k k子集的或之后还是 k k k子集。

现在我们来研究如何计算 f w t ( A ) fwt(A) fwt(A),类似 FFT \text{FFT} FFT地用分治的方法,设 A 0 A_0 A0为当前位为 0 0 0的项, A 1 A_1 A1就是剩下部分:
f w t ( A ) = { ( f w t ( A 0 ) , f w t ( A 0 + A 1 ) ) n > 0 A n = 0 fwt(A)=\begin{cases}(fwt(A_0),fwt(A_0+A_1))& n>0\\A& n=0\end{cases} fwt(A)={(fwt(A0),fwt(A0+A1))An>0n=0根据 f w t ( A ) fwt(A) fwt(A)的定义可以知道: f w t ( A + B ) = f w t ( A ) + f w t ( B ) fwt(A+B)=fwt(A)+fwt(B) fwt(A+B)=fwt(A)+fwt(B),根据定义也可以知道如果最高位为 1 1 1,那么就把 f w t ( A 0 + A 1 ) fwt(A_0+A_1) fwt(A0+A1)算作 A A A的后半部分就可以了,为 0 0 0的话子集就是 0 0 0

现在我们来证明一下 f w t ( A ∣ B ) = f w t ( A ) × f w t ( B ) fwt(A|B)=fwt(A)\times fwt(B) fwt(AB)=fwt(A)×fwt(B)
f w t ( A ∣ B ) = f w t ( ( A ∣ B ) 0 , ( A ∣ B ) 1 ) = f w t ( A 0 ∣ B 0 , A 0 ∣ B 1 + A 1 ∣ B 0 + A 1 ∣ B 1 ) = ( f w t ( A 0 ∣ B 0 ) , f w t ( A 0 ∣ B 0 + A 1 ∣ B 0 + A 0 ∣ B 1 + A 1 B 1 ) ) = ( f w t ( A 0 ) × f w t ( B 0 ) , f w t ( A 0 + A 1 ) × f w t ( B 0 + B 1 ) ) = ( f w t ( A 0 ) , f w t ( A 0 + A 1 ) ) × ( f w t ( B 0 ) , f w t ( B 0 , B 1 ) ) = f w t ( A ) × f w t ( B ) fwt(A|B)=fwt((A|B)_0,(A|B)_1)\\=fwt(A_0|B_0,A_0|B_1+A_1|B_0+A_1|B_1)\\=(fwt(A_0|B_0),fwt(A_0|B_0+A_1|B_0+A_0|B_1+A_1B_1))\\=(fwt(A_0)\times fwt(B_0),fwt(A_0+A_1)\times fwt(B_0+B_1))\\=(fwt(A_0),fwt(A_0+A_1))\times (fwt(B_0),fwt(B_0,B_1))\\=fwt(A)\times fwt(B) fwt(AB)=fwt((AB)0,(AB)1)=fwt(A0B0,A0B1+A1B0+A1B1)=(fwt(A0B0),fwt(A0B0+A1B0+A0B1+A1B1))=(fwt(A0)×fwt(B0),fwt(A0+A1)×fwt(B0+B1))=(fwt(A0),fwt(A0+A1))×(fwt(B0),fwt(B0,B1))=fwt(A)×fwt(B)这里用到了数学归纳法,首先 n = 0 n=0 n=0的情况肯定成立,然后我们假设较小的规模成立,以此推导更大的规模(这里是 1 2 \frac{1}{2} 21

最后返回来的 d f w t dfwt dfwt就是根据上面的 f w t fwt fwt设计的,变换如下:
d f w t ( A ) = { d f w t ( A 0 ) , d f w t ( A 1 − A 0 ) n > 1 A n = 0 dfwt(A)=\begin{cases}dfwt(A_0),dfwt(A_1-A_0)&n>1\\A&n=0\end{cases} dfwt(A)={dfwt(A0),dfwt(A1A0)An>1n=0

and 卷积

这个和上面的卷积极其类似,直接给出结论:
f w t ( A ) = { ( f w t ( A 0 + A 1 ) , f w t ( A 0 ) ) n > 0 A n = 0 fwt(A)=\begin{cases}(fwt(A_0+A_1),fwt(A_0))& n>0\\A& n=0\end{cases} fwt(A)={(fwt(A0+A1),fwt(A0))An>0n=0逆变换如下:
d f w t ( A ) = { d f w t ( A 0 − A 1 ) , d f w t ( A 1 ) n > 0 A n = 0 dfwt(A)=\begin{cases}dfwt(A_0-A_1),dfwt(A_1)&n>0\\A&n=0\end{cases} dfwt(A)={dfwt(A0A1),dfwt(A1)An>0n=0

xor 卷积

这就是重头戏了,我们要解决: C k = ∑ i ⊕ j = k A i B j C_k=\sum_{i\oplus j=k}A_iB_j Ck=ij=kAiBj

先定义 f w t ( A ) [ x ] = ∑ 2 ∣ d ( x ∩ i ) A i − ∑ 2 ∣ [ d ( x ∩ i ) − 1 ] A i fwt(A)[x]=\sum_{2|d(x\cap i)}A_i-\sum_{2|[d(x\cap i)-1]}A_i fwt(A)[x]=2d(xi)Ai2[d(xi)1]Ai d d d是二进制 1 1 1的个数,这样定义是基于一个结论:
d ( x ∩ ( i ⊕ j ) ) = d ( x ∩ i ) + d ( x ∩ j ) − 2 d ( x ∩ i ∩ j ) d(x\cap (i\oplus j))=d(x\cap i)+d(x\cap j)-2d(x\cap i\cap j) d(x(ij))=d(xi)+d(xj)2d(xij)考虑每一位的合法性,就可以推知所有情况,这个自己枚举一下可能的组合就行了,然后:
f w t ( A ) [ x ] ⊕ f w t ( B ) [ x ] = ∑ 2 ∣ d ( x ∩ i ) ∑ 2 ∣ d ( x ∩ j ) A i B j − ∑ 2 ∣ [ d ( x ∩ i ) − 1 ] ∑ 2 ∣ d ( x ∩ j ) A i B j . . . . . . fwt(A)[x]\oplus fwt(B)[x]=\sum_{2|d(x\cap i)}\sum_{2|d(x\cap j)}A_iB_j-\sum_{2|[d(x\cap i)-1]}\sum_{2|d(x\cap j)}A_iB_j...... fwt(A)[x]fwt(B)[x]=2d(xi)2d(xj)AiBj2[d(xi)1]2d(xj)AiBj......你可以发现如果 d ( x ∩ i ) d(x\cap i) d(xi) d ( x ∩ j ) d(x\cap j) d(xj)奇偶性相同的话那么前面的符号是正,否则前面的符号是负,我们观察上面的结论,发现 d ( x ∩ ( i ⊕ j ) ) d(x\cap (i\oplus j)) d(x(ij)) d ( x ∩ i ) + d ( x ∩ j ) d(x\cap i)+d(x\cap j) d(xi)+d(xj)的奇偶性相同,恰好就对应了。

现在来考虑正变换,基本思路还是考虑最高位,左半边的话都选自己和选自己再选对半边都可以,而且不需要变号,因为不会产生新的 1 1 1位,两个都选右半边的话就需要变号,那么就这样算:
f w t ( A ) = { ( f w t ( A 0 + A 1 ) , f w t ( A 0 − A 1 ) ) n > 0 A n = 0 fwt(A)=\begin{cases}(fwt(A_0+A_1),fwt(A_0-A_1))&n>0\\A&n=0\end{cases} fwt(A)={(fwt(A0+A1),fwt(A0A1))An>0n=0然后逆变化就是: i f w t ( A ) = { ( i f w t ( A 0 + A 1 ) 2 , i f w t ( A 0 − A 1 ) 2 ) n > 0 A n = 0 ifwt(A)=\begin{cases}(\frac{ifwt(A_0+A_1)}{2},\frac{ifwt(A_0-A_1)}{2})&n>0\\A&n=0\end{cases} ifwt(A)={(2ifwt(A0+A1),2ifwt(A0A1))An>0n=0
然后贴一个板题的代码:

#include <cstdio>
const int M = 200005;
const int MOD = 998244353;
int read()
{
    int num=0,flag=1;char c;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
    while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
    return num*flag;
}
int n,a[M],b[M],A[M],B[M],inv2=(MOD+1)/2;
void fwt_or(int *a,int op)
{
	for(int i=1;i<n;i<<=1)
		for(int p=i<<1,j=0;j<n;j+=p)
			for(int k=0;k<i;k++)
			{
				if(op==1) a[i+j+k]=(a[i+j+k]+a[j+k])%MOD;
				else a[i+j+k]=(a[i+j+k]-a[j+k]+MOD)%MOD;
			}
}
void fwt_and(int *a,int op)
{
	for(int i=1;i<n;i<<=1)
		for(int p=i<<1,j=0;j<n;j+=p)
			for(int k=0;k<i;k++)
			{
				if(op==1) a[j+k]=(a[j+k]+a[i+j+k])%MOD;
				else a[j+k]=(a[j+k]-a[i+j+k]+MOD)%MOD;
			}
}
void fwt_xor(int *a,int op)
{
	for(int i=1;i<n;i<<=1)
		for(int p=i<<1,j=0;j<n;j+=p)
			for(int k=0;k<i;k++)
			{
				int x=a[j+k],y=a[i+j+k];
				a[j+k]=(x+y)%MOD;
				a[i+j+k]=(x+MOD-y)%MOD;
				if(op==-1)
				{
					a[j+k]=1ll*a[j+k]*inv2%MOD;
					a[i+j+k]=1ll*a[i+j+k]*inv2%MOD;
				}
			}
}
void init()
{
	for(int i=0;i<n;i++)
		A[i]=a[i],B[i]=b[i];
}
signed main()
{
	n=1<<read();
	for(int i=0;i<n;i++) a[i]=read();
	for(int i=0;i<n;i++) b[i]=read();
	init();
	fwt_or(A,1);fwt_or(B,1);
	for(int i=0;i<n;i++) A[i]=1ll*A[i]*B[i]%MOD;
	fwt_or(A,-1);
	for(int i=0;i<n;i++) printf("%d ",A[i]);
	puts("");
	init();
	fwt_and(A,1);fwt_and(B,1);
	for(int i=0;i<n;i++) A[i]=1ll*A[i]*B[i]%MOD;
	fwt_and(A,-1);
	for(int i=0;i<n;i++) printf("%d ",A[i]);
	puts("");
	init();
	fwt_xor(A,1);fwt_xor(B,1);
	for(int i=0;i<n;i++) A[i]=1ll*A[i]*B[i]%MOD;
	fwt_xor(A,-1);
	for(int i=0;i<n;i++) printf("%d ",A[i]);
	puts("");
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值