快速莫比乌斯/沃尔什变换 (FMT/FWT)

快速莫比乌斯/沃尔什变换 (FMT/FWT)

这个东西是用来求 二进制位运算 的卷积的,\(or\)卷积、\(and\)卷积、\(xor\)卷积。

引入

我们要求的是:

\[C[i]=\sum_{i=j\oplus k}A[j]*B[k] \]

考虑像 FFT 一样,先将一个式子计算出它的正变换后的式子,再相乘,最后做一次逆变换。于是我们先定义一个式子:

\[fwt_A[i]=\sum_{j=0}^nc(i,j)*A[j] \]

其中,\(fwt_A\) 表示 \(A\) 经过 \(FWT\) 变换后的数组,\(c(i,j)\) 表示一个贡献函数,现在还没有求出来。

然后,我们需要它满足(后面那个是按位乘):

\[C=A*B\Leftrightarrow fet_C=fwt_A\times fwt_B \]

接着,我们继续推:

\[\begin{aligned} fet_C[i]&=\sum_{i=0}^n fwt_A[i]*fwt_B[i]\\ \sum_{i=0}^nc(i,j)*C[j]&=\sum_{i=0}^n(\sum_{j=0}^nc(i,j)*A[j])(sum_{k=0}^nc(i,k)*B[k])\\ \sum_{i=0}^nc(i,p)*\sum_{p=j\oplus k}A[j]*B[k] &=\sum_{i=0}^n\sum_{j=0}^n\sum_{k=0}^nc(i,j)*c(i,k)*A[j]*B[k]\\ \sum_{i=0}^n\sum_{p=j\oplus k}c(i,j\oplus k)*A[j]*B[k] &=\sum_{i=0}^n\sum_{j=0}^n\sum_{k=0}^nc(i,j)*c(i,k)*A[j]*B[k] \end{aligned} \]

于是我们可以得到 \(c(i,j)*c(i,k)=c(i,j\oplus k)\) ,又因为它是位运算,所以我们可以把它按位拆开来处理。并且对于一个数,我们将它 每一位拆开后的数 的贡献系数乘起来的结果作为它的贡献系数。

接下来我们继续考虑求这个式子;

\[fwt_A[i]=\sum_{j=0}^nc(i,j)*A[j] \]

考虑像 FFT 一样拆成两半,设 \(A_0\) 表示序列 \(A\) 中,下标二进制最高位的值为 0;\(A_1\) 同理。那么我们可以得到这样的式子:

\[fwt_A[i]=\sum_{j=0}^{n/2}c(i,j)*A[j]+\sum_{j=n/2+1}^{n}c(i,j)*A[j] \]

然后我们对首位拆开来考虑,设 \(i'\) 表示 \(i\) 去掉首位后的值:

\[=c(i^0,0)*\sum_{j=0}^{n/2}c(i',j')*A[j]+c(i^0,1)*\sum_{j=n/2+1}^{n}c(i’,j')*A[j] \]

接着分类讨论 \(i\) 的首位取 0 还是 1。

\[fwt_A[i]=c(0,0)*fwt_{A_0}[i]+c(0,1)*fwt_{A_1}[i]\\ fwt_A[i+n/2]=c(1,0)*fwt_{A_0}[i]+c(1,1)*fwt_{A_1}[i] \]

最终我们得到了这样的式子,与 FFT 类似,我们可以分治。对于求出 \(c([0,1],[0,1])\) ,我们可以使用矩阵,或者再去推式子。把这些写出来的原因是,我们可以使用矩阵求逆,求出逆变换时的系数来做逆变换,自己并不是很能理解用其他方法来计算逆运算时的系数。

OR卷积

对于 \(or\)卷积,我们要求的就是:

\[fwt_C[i]=\sum_{j|i=i}C[j] \]

而:

\[\begin{aligned} fwt_A[i]*fwt_B[i]&=(\sum_{j|i=i}A[j])*(\sum_{k|i=i}B[k])\\ &=\sum_{(j|k)|i=i}A[j]*B[k]\\ &=\sum_{(j|k)|i=i}C[j|k]\\ &=fwt_C[i] \end{aligned} \]

接着我们考虑前面那个分治的式子:

\[fwt_A[i]=c(0,0)*fwt_{A_0}[i]+c(0,1)*fwt_{A_1}[i]\\ fwt_A[i+n/2]=c(1,0)*fwt_{A_0}[i]+c(1,1)*fwt_{A_1}[i] \]

此时我们容易发现,对于 \(fwt_{A_1}[i]\) ,它无法通过 or 运算得到 \(i-n/2\) ,所以 \(c(0,1)\) 应该为 0;而其他的都可以转移到,都为 1。

然后我们再通过求矩阵的逆 ,可以得到矩阵 \(\begin{bmatrix} 1&0 \\ 1&1 \end{bmatrix}\) 的逆为:\(\begin{bmatrix} 1&0 \\ -1&1 \end{bmatrix}\)

所以 \(or\)卷积 的正变换式子为:

\[fwt_A[i]=fwt_{A_0}[i]\\ fwt_A[i+n/2]=fwt_{A_0}[i]+fwt_{A_1}[i] \]

逆变换式子为:

\[Ifwt_A[i]=Ifwt_{A_0}[i]\\ Ifwt_A[i+n/2]=Ifwt_{A_1}[i]-Ifwt_{A_0}[i] \]

最终我们把系数代入,分治求答案就好了。

AND卷积

\(or\)卷积 类似,定义式子 \(fwt_A[i]=\sum_{j\&i=i}A[j]\) ,我们容易得到 \(fwt_A[i]*fwt_B[i]=fwt_C[i]\) 。然后考虑贡献系数:

\[fwt_A[i]=c(0,0)*fwt_{A_0}[i]+c(0,1)*fwt_{A_1}[i]\\ fwt_A[i+n/2]=c(1,0)*fwt_{A_0}[i]+c(1,1)*fwt_{A_1}[i] \]

此时 \(i\) 不可能通过 and运算 得到 \(i+n/2\) ,所以此时的 \(c(1,0)\) 应为 0,其余为 1 。所以我们得到矩阵 \(\begin{bmatrix} 1&1 \\ 0&1 \end{bmatrix}\) ,对其求逆可得 \(\begin{bmatrix} 1&-1 \\ 0&1 \end{bmatrix}\) 。于是我们得到了正变换的式子:

\[fwt_A[i]=fwt_{A_0}[i]+fwt_{A_1}[i]\\ fwt_A[i+n/2]=fwt_{A_1}[i] \]

逆变换的式子:

\[Ifwt_A[i]=Ifwt_{A_0}[i]-Ifwt_{A_1}[i]\\ Ifwt_A[i+n/2]=Ifwt_{A_1}[i] \]

XOR卷积

这部分使用 \(\oplus\) 表示 按位异或 运算。

我们根据 \(c(i,j)*c(i,k)=c(i,j\oplus k)\) 来推导贡献系数。

根据 \(c(0,0)*c(0,x)=c(0,x\oplus0)=c(0,x)\) ,我们可以得到 \(c(0,0)=1\)

由于 \(c(1,1)*c(1,1)=c(1,0)\) ,此时如果填 0,那么矩阵没有逆,所以这两个数都非 0;

又因为 \(c(1,0)*c(1,0)=c(1,0)\) ,所以此时的 \(c(1,0)=\pm1\) ,而如果 \(c(1,0)=-1\) ,则 \(c(1,1)\) 无实数解,所以我们令 \(c(1,0)=1\) ,而 \(c(1,1)=\pm1\)

由于 \(c(0,1)*c(0,1)=c(0,0)\) ,所以 \(c(0,1)=\pm1\)

而当 \(c(0,1)=c(1,1)\) 时,矩阵没有逆。所以两个取异号。我们这里使用 \(c(0,1)=1,c(1,1)=-1\)

然后我们就能够构造出转移系数的矩阵 \(\begin{bmatrix} 1&1 \\ 1&-1 \end{bmatrix}\) 了,对其求逆得到 \(\begin{bmatrix} 0.5&0.5 \\ 0.5&-0.5 \end{bmatrix}\)

正变换式子:

\[fwt_A[i]=fwt_{A_0}[i]+fwt_{A_1}[i]\\ fwt_A[i+n/2]=fwt_{A_0}[i]-fwt_{A_1}[i] \]

逆变换式子:

\[Ifwt_A[i]=\frac{Ifwt_{A_0}[i]+Ifwt_{A_1}[i]}2\\ Ifwt_A[i+n/2]=\frac{Ifwt_{A_0}[i]-Ifwt_{A_1}[i]}2 \]

最终,我们得出了以上三种卷积的 \(O(n\log n)\) 的计算方法。

Code

#include<cstdio>
using namespace std;
const int N=1e6+5,M=998244353,NY=499122177;
long long a[N],b[N],c[N],ansa[N],ansb[N],ansc[N];
int n;
void OR(int flag)
{
	int i,j,len,p;
	for(len=2,p=1;len<=n;len=len<<1,p=p<<1)
	{
		for(i=0;i<n;i+=len)
		{
			for(j=0;j<p;j++)
			c[i+j+p]=(c[i+j+p]+c[i+j]*flag+M)%M;
		}
	}
}
void get_OR()
{
	int i;
	for(i=0;i<n;i++)
	c[i]=a[i];
	OR(1);
	for(i=0;i<n;i++)
	{
		ansa[i]=c[i];c[i]=b[i];
	}
	OR(1);
	for(i=0;i<n;i++)
	c[i]=ansa[i]*c[i]%M;
	OR(-1);
	for(i=0;i<n;i++)
	ansa[i]=c[i];
}

void AND(int flag)
{
	int i,j,len,p;
	for(len=2,p=1;len<=n;len=len<<1,p=p<<1)
	{
		for(i=0;i<n;i+=len)
		{
			for(j=0;j<p;j++)
			c[i+j]=(c[i+j+p]*flag+c[i+j]+M)%M;
		}
	}
}
void get_AND()
{
	int i;
	for(i=0;i<n;i++)
	c[i]=a[i];
	AND(1);
	for(i=0;i<n;i++)
	{
		ansb[i]=c[i];c[i]=b[i];
	}
	AND(1);
	for(i=0;i<n;i++)
	c[i]=ansb[i]*c[i]%M;
	AND(-1);
	for(i=0;i<n;i++)
	ansb[i]=c[i];
}

void XOR(long long x)
{
	int i,j,len,p;long long t;
	for(len=2,p=1;len<=n;len=len<<1,p=p<<1)
	{
		for(i=0;i<n;i+=len)
		{
			for(j=0;j<p;j++)
			{
				t=c[i+j+p];
				c[i+j+p]=(c[i+j]-t+M)*x%M;
				c[i+j]=(c[i+j]+t)*x%M;
			}
		}
	}
}
void get_XOR()
{
	int i;
	for(i=0;i<n;i++)
	c[i]=a[i];
	XOR(1);
	for(i=0;i<n;i++)
	{
		ansc[i]=c[i];c[i]=b[i];
	}
	XOR(1);
	for(i=0;i<n;i++)
	c[i]=ansc[i]*c[i]%M;
	XOR(NY);
	for(i=0;i<n;i++)
	ansc[i]=c[i];
}
int main()
{
	int i;
	scanf("%d",&n);
	n=(1<<n);
	for(i=0;i<n;i++)
	scanf("%lld",&a[i]);
	for(i=0;i<n;i++)
	scanf("%lld",&b[i]);
	get_OR();
	get_AND();
	get_XOR();
	for(i=0;i<n;i++)
	printf("%lld ",ansa[i]);
	printf("\n");
	for(i=0;i<n;i++)
	printf("%lld ",ansb[i]);
	printf("\n");
	for(i=0;i<n;i++)
	printf("%lld ",ansc[i]);
	return 0;
}
posted @ 2024-08-22 09:30  Cyan_wind  阅读(11)  评论(0编辑  收藏  举报