FWT(1)

概述

FWT 常用于解决以下式子的 \(O(n\log n)\) 求解:

\[C[k]=(A*B)[k]=\sum_{i\oplus j=k}A[i]B[j],\text{where }\oplus=\text{and/or/xor} \]

FWT 的思路和 FFT 类似,先将多项式 \(A,B\) 转化为一些特殊的点值 \(FWT(A)_0^{n-1},FWT(B)_0^{n-1}\),再将对应点值相乘求出 \(FWT(C)_i=FWT(A)_iFWT(B)_i\),然后反着做,求出 \(C\)
这些特殊的点值前人已经告诉我们求哪 \(n\) 个,我们只用知道怎么求出它们,即 \(FWT(A),FWT(B)\);同时还需要知道怎么做 IFWT,将点值转回去。

对于不同的按位运算符,FWT 的运算有一些不同,但思路类似;其中,| 和 & 的本质相近,它们的 FWT/IFWT 也很相近。

正文

|

FWT(A)

定义

\[FWT(A)[i]=\sum_{j\in i}A[j] \]

其中 \(j\in i\) 表示 \(j\) 的二进制表示的 1 的位置集合含于 \(i\) 的二进制表示中 1 的位置集合。

递推

首先显然 \(FWT(A_i)=A_i\),即一个数构成的集合的 FWT 就是这个数。

公式

\[FWT(A)=(FWT(A_0),FWT(A_0)+FWT(A_1)) \]

解释

符号解释:\(A_0,A_1\) 分别表示长度为 \(2^n\) 次多项式的前一半和后一半,其中 \(A_0\) 的下标我们从 \(0\)\(2^{n-1}-1\)\(A_1\) 的下标我们从 \(2^{n-1}\)\(2^n-1\)\((A,B)\) 表示 连接 \(A,B\) 两个多项式。

考虑定义,\(\forall i\in [0,2^{n-1}-1],\in i\)\(j\) 一定 \(\in [0,2^{n-1}-1]\),所以 \(FWT(A)_0=FWT(A_0)\)\(\forall i\in [2^{n-1},2^n-1]\)\(\in i\)\(j\)\([0,2^{n-1}-1]\)\([0,2^{n-1}-1]\) 分别出现在 \(p_1,p_2,...,p_u\)\(q_1,q_2,...,q_u\),并且满足 \(q_i=p_i+2^{n-1}\),因此对于 \(p\) 的部分,\(FWT(A)_1\gets FWT(A_0)\),对于 \(q\) 的部分,\(FWT(A)_1\gets FWT(A_1)\)

IFWT(A)

递推

首先显然 \(IFWT(A_i)=A_i\),即一个数构成的集合的 IFWT 就是这个数。

公式

\[IFWT(A)=(IFWT(A_0),IFWT(A_0)-IFWT(A_1)) \]

(就是把 + 换成了 -)

解释

\(\because FWT(A)_0=FWT(A_0),FWT(A)_1=FWT(A_0)+FWT(A_1)\)
\(\therefore A_0=IFWT(FWT(A_0))=IFWT(FWT(A)_0),A_1=IFWT(FWT(A_1))=IFWT(FWT(A)_1-FWT(A_0))=IFWT(FWT(A)_1-FWT(A)_0)\)
注:你可能会说,\(IFWT(FWT(A)_1-FWT(A)_0)\) 为什么 = \(IFWT(FWT(A)_1)-IFWT(FWT(A)_0)\)?这个显然,因为 FWT 和 IFWT 其实本质上都是将同一个多项式在不同表示法中间变换而已。

实现

能够一直递推到长为 1 的区间需要满足 \(n\) 是 2 的幂,所以如果不是就要补成 \(2^m\)。按位 | 部分的代码如下:

void FWT(int *arr,int type)://type=1->FWT,type=0->IFWT
for(int w=1;w<(1<<m);w<<=1)
	for(int j=0;j<(1<<m);j+=w<<1)
		for(int i=j;i<j+w;i++)
			(arr[i+w]+=(arr[i]*(type?1:-1)+mod)%mod)%=mod;

&

& 和 | 非常类似,所以不推了,如果你想重复一次,可以参照 这篇博客

FWT

递推

公式

\[FWT(A)=(FWT(A_0)+FWT(A_1),FWT(A_1)) \]

IFWT

递推

公式

\[IFWT(A)=(IFWT(A_0)-IFWT(A_1),IFWT(A_1)) \]

实现

void FWT(int *arr,int type):
for(int w=1;w<(1<<m);w<<=1)
	for(int j=0;j<(1<<m);j+=w<<1)
		for(int i=j;i<j+w;i++)
			(arr[i]+=(arr[i+w]*(type?1:-1)+mod)%mod)%=mod;

^

xor 是一种完全不同的运算。

FWT(A)

定义

\[FWT(A)[i]=\sum_{j=0}^{2^n-1}(-1)^{c(i\&j)}A[j] \]

其中记 \(\text{popcount}(x)=c(x)\)

递推

首先显然 \(FWT(A_i)=A_i\),即一个数构成的集合的 FWT 就是这个数。

公式

\[FWT(A)=(FWT(A_0)+FWT(A_1),FWT(A_0)-FWT(A_1)) \]

注:这里的 \(A_0,A_1\) 的下标都是从 \(0\)\(2^{n-1}-1\)

解释

\[\forall i\in [0,2^{n-1}-1],FWT(A)[i]=\sum_{j=0}^{2^{n-1}-1}(-1)^{c(i\&j)}A[j]+\sum_{j=2^{n-1}}^{2^n-1}(-1)^{c(i\&j)}A[j]\\ =\sum_{j=0}^{2^{n-1}-1}(-1)^{c(i\&j)}A[j]+\sum_{j=0}^{2^{n-1}-1}(-1)^{c(i\&j)}A[j+2^{n-1}]=FWT(A_0)+FWT(A_1)\]

说一下第二排的第一个等号,是因为 \(\forall i\in [0,2^{n-1}-1]\)\(i\&j=i\&(j+2^{n-1})\),这是因为 \(i\) 的最高位是 \(0\)

\[\forall i\in [2^{n-1},2^n-1],FWT(A)[i]=\sum_{j=0}^{2^{n-1}-1}(-1)^{c(i\&j)}A[j]+\sum_{j=2^{n-1}}^{2^n-1}(-1)^{c(i\&j)}A[j]\\ =\sum_{j=0}^{2^{n-1}-1}(-1)^{c(i\&j)}A[j]-\sum_{j=0}^{2^{n-1}-1}(-1)^{c(i\&j)}A[j+2^{n-1}]=FWT(A_0)-FWT(A_1)\]

说一下第二排的第一个等号,是因为 \(\forall i\in [0,2^{n-1}-1]\)\(i\&j+2^{n-1}=i\&(j+2^{n-1})\),这是因为 \(i\) 的最高位是 \(1\),而 \(j\) 的最高位是 \(0\),因此 \({c(i\&j)}\)\(c(i\&(j+2^{n-1}))\) 就相差了 \(1\)

IFWT(A)

递推

首先显然 \(IFWT(A_i)=A_i\),即一个数构成的集合的 IFWT 就是这个数。

公式

\[IFWT(A)=(\frac{IFWT(A_0)+IFWT(A_1)}{2},\frac{IFWT(A_0)-IFWT(A_1)}{2}) \]

(就是把 ×1 换成了 ×½)

解释

\(\because FWT(A)_0=FWT(A_0)+FWT(A_1),FWT(A)_1=FWT(A_0)-FWT(A_1)\)
(两式相加除以 2)\(\therefore A_0=IFWT(\frac{FWT(A)_0+FWT(A)_1}2),\)
(两式相减除以 2)\(A_1=IFWT(\frac{FWT(A)_0-FWT(A)_1}2)\)

实现

void FWT(int *arr,int type):
for(int w=1;w<(1<<m);w<<=1)
	for(int j=0;j<(1<<m);j+=w<<1)
		for(int i=j;i<j+w;i++){
			int u=arr[i]+arr[i+w],v=arr[i]-arr[i+w];
			arr[i]=1ll*u*(type?1:I2)%mod,arr[i+w]=1ll*(v+mod)*(type?1:I2)%mod;
		}

定义的原因

你可能很疑惑,为什么要定义成那种奇形怪状的式子呢?原因在于它们相乘能够得到正确的 \(FWT(A*B)[i]\)

|

\[FWT(A)[i]FWT(B)[i]=\sum_{j\in i}A[j]\sum_{j\in i}B[j]\\=\sum_{j\in i,k\in i}A[j]B[k]\\=\sum_{j|k\in i}A[j]B[k]\\ =\sum_{x\in i}\sum_{j|k=x}A[j]B[k]=FWT(C)[i]\]

&

\[FWT(A)[i]FWT(B)[i]=\sum_{i\in j}A[j]\sum_{i\in j}B[j]\\=\sum_{i\in j,i\in k}A[j]B[k]\\=\sum_{i\in j\&k}A[j]B[k]\\ =\sum_{x\in i}\sum_{j\&k=x}A[j]B[k]=FWT(C)[i]\]

^

\(P(x)\)\(x\) 的奇偶性(即 mod 2)。

引理:\(P({c(i\&j)})\operatorname{xor}P(c(i\&k))=P(c(i\&(j\operatorname{xor}k)))\)。感性理解或查看 刚才的链接

\[FWT(A)[i]FWT(B)[i]=\sum_{j=0}^{2^n-1}(-1)^{{c(i\&j)}}A[j]\sum_{j=0}^{2^n-1}(-1)^{{c(i\&j)}}B[j]\\=\sum_{0\le j,k\le 2^n-1}(-1)^{{c(i\&j)}}A[j](-1)^{c(i\&k)}B[k]\\ =\sum_{0\le j,k\le 2^n-1}(-1)^{{c(i\&j)}+c(i\&k)}A[j]B[k]\\ =\sum_{0\le j,k\le 2^n-1}(-1)^{P({c(i\&j)})+P(c(i\&k))}A[j]B[k]\\ =\sum_{0\le j,k\le 2^n-1}(-1)^{P({c(i\&j)})\operatorname{xor}P(c(i\&k))}A[j]B[k]\\ =\sum_{0\le j,k\le 2^n-1}(-1)^{P(c(i\&(j\operatorname{xor}k)))}A[j]B[k] =\sum_{x=0}^{2^n-1}\sum_{j\operatorname{xor}k=x}(-1)^{c(i\&x)}A[j]B[k]\\ =\sum_{x=0}^{2^n-1}(-1)^{c(i\&x)}\sum_{j\operatorname{xor}k=x}A[j]B[k]\\ =FWT(C)[i]\]

类 FWT

Eg.1

我们 for 循环的从小到大枚举 w 显然是将两个小块合并为一个大块的倍增过程,并且假如设 \(w=2^h\),那么我们正在处理下标的二进制第 \(h\) 位(从低到高为从 \(0\)\(m-1\)。不信你可以看我们每次“递推”的过程,其实依据的是那次递推的最高位的运算性质,来将更低的位们的运算结果变成当前所有位的运算结果。
这个原理有什么用呢?这说明 FWT 也是“按位”的。也就是说,如果定义一种新运算 \(\otimes\),它对不同的二进制位采用不同的按位运算符,那我们也可以用“在不同的 w 的轮中使用不同的按位运算符”来求出一种“混合沃尔什变换”。形如:

void fwt(int *arr,int type){
	for(int w=1,o=0;o<=m-1;w<<=1,o++){
		if(b[o]==0)continue;
		else if(b[o]==1){
			for(int j=0;j<(1<<m);j+=w<<1)
				for(int i=j;i<j+w;i++)
					(arr[i+w]+=(arr[i]*(type?1:-1)+mod)%mod)%=mod;
		}
		else if(b[o]==2){
			for(int j=0;j<(1<<m);j+=w<<1)
				for(int i=j;i<j+w;i++)
					(arr[i]+=(arr[i+w]*(type?1:-1)+mod)%mod)%=mod;
		}
		else {
			for(int j=0;j<(1<<m);j+=w<<1)
				for(int i=j;i<j+w;i++){
					int u=arr[i]+arr[i+w],v=arr[i]-arr[i+w];
					arr[i]=1ll*u*(type?1:I2)%mod,arr[i+w]=1ll*(v+mod)*(type?1:I2)%mod;
				}
		}
	}
}

事实上,这道模拟赛题 的核心就是这。另外,这道题是将 dp 数组 \(f_i[0],f_i[1],...,f_i[m-1]\) 看成多项式 \(f_i\),并可以显然地得到 \(f_1=T\)\(f_i=f_{i-1}\otimes T(i\ge 2)\),FWT => 点值快速幂 => IFWT,就得到了最后答案。

Eg.2 特工

化简发现 \(b_i=\sum_{P(c(j))=P({c(i\&j)})}a_j\),递推得 \(FWT(A)_0=FWT(A_0)+sum(A_1)-FWT(A_1),FWT(A)_1=FWT(A_1)+FWT(A_0)\),考虑到 \(FWT(A)_1\) 基本就是个块内前缀和了,所以 \(sum(A_1)=FWT(A)[j+2w-1]-FWT(A)[j+w-1]\) 代入,那么 IFWT 的实现方式可以就 \(w\) 倒着来一次,把上面移项化成 \(FWT(A_0)=...,FWT(A_1)=...\) 就行了。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1.5e6+5;
int n,b[N];
void IFWT(int *arr){
	for(int w=n>>1;w;w>>=1)
		for(int j=0;j<n;j+=w<<1){
			int sum=arr[j+(w<<1)-1]-arr[j+w-1];
			for(int i=j;i<j+w;i++){
				int x=arr[i],y=arr[i+w];
				arr[i]=(x+y-sum)/2,arr[i+w]=(y-x+sum)/2;
			}
		}
}
signed main(){
	scanf("%lld",&n);
	for(int i=0;i<n;i++)scanf("%lld",&b[i]);
	IFWT(b);
	for(int i=0;i<n;i++)cout<<b[i]<<' ';
}

Eg.3 魔法小程序

首先看懂他在写什么,发现类似于一个“混合进制”,就把一个数拆成了很多位。很好想到可以把 \(a\) 里头的 1 都删掉,这样不影响,从而将每个数的位数变成 log。等价于是 \(j\) 每一位都 \(\le i\) 就把 \(b_j\) 加给 \(c_i\)。这个跟 FWT 其实就是进制的区别。FWT 怎么分的呢,是把一个 \(2w\) 大块变成了两个 \(w\) 小块;如法炮制,把一个 \(a_iw\) 大块划分成 \(a_i\)\(w\) 小块。IFWT 还是采用好想的倒着来一遍方式。

#include <bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
	register char ch=getchar();register int x=0,f=1;
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return x*f;
}
void print(int x){
	if(x<0){putchar('-'),print(-x);return;}
	if(x/10)print(x/10);
	putchar(x%10+48);
}
const int N=1e6+5;
int n,m,a[N],c[N];
signed main(){
	n=read(),print(n),puts("");
	for(int i=1;i<=n;i++)a[i]=read(),print(a[i]),putchar(' ');puts("");a[++n]=1e9;
	m=read(),print(m),puts("");
	for(int i=0;i<m;i++)c[i]=read();
	int nn=0;a[0]=1e9;
	for(int i=1;i<=n;i++)if(a[i]!=1)a[++nn]=a[i];
	n=nn;n=min(n,17ll);
	int w=1,ii=1;
	while(w<m)w*=a[ii++];
	w/=a[--ii];
	for(int i=ii;w;i--,w/=a[i]){
		for(int j=0;j<m;j+=w*a[i])
			for(int k=min(j+w*a[i]-1,m-1);k>=j;k--)if(k-w>=j)c[k]-=c[k-w];
	}
	for(int i=0;i<m;i++)print(c[i]),putchar(' ');
}
posted @ 2022-07-18 20:19  pengyule  阅读(74)  评论(0)    收藏  举报