首页 > 其他分享 >快速莫比乌斯/沃尔什变换 (FMT/FWT)

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

时间:2024-08-22 09:25:59浏览次数:10  
标签:卷积 FMT Ifwt fwt 沃尔什 bmatrix FWT sum 式子

快速莫比乌斯/沃尔什变换 (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;
}

标签:卷积,FMT,Ifwt,fwt,沃尔什,bmatrix,FWT,sum,式子
From: https://www.cnblogs.com/Cyanwind/p/18373051

相关文章

  • C++ fmt
    Input/outputlibrarycppreferenceInput/outputlibrary有三种io库:OOP样式,基于流的io库基于print的系列函数C样式的IO函数基于流的'io'库基于流的'io'库是围绕抽象的io设备来进行组织的,这些抽象的设备允许使用相同的代码处理输入/输出文件,内存流,或定制......
  • 在 Go 中,`fmt.Printf` 常用的 `%` 占位符类型
    在Go中,fmt.Printf常用的%占位符类型如下:%v:值的默认格式表示。%+v:结构体字段名和值的格式表示。%#v:Go语法表示的值。%T:值的类型。%%:百分号字面量。对于特定类型:%d:整数(十进制)。%b:整数(二进制)。%o:整数(八进制)。%x,%X:整数(十六进制)。%f:浮点数。%e,%E:科学......
  • 如何在 GoLand 中使用 gofmt 和 goimports 工具
    如何在GoLand中使用gofmt和goimports工具参考文章GoLand是JetBrains公司开发的一款Go语言集成开发环境(IDE),拥有丰富的代码自动补全、错误提示和代码重构等功能,极大地提高了编程效率。Go语言有一套自带的代码格式化工具——gofmt,它能够自动将非标准的Go代码格式化为......
  • 格式化字符串(summer2024_fmt)
    参考博客[参考博客]:https://blog.csdn.net/ysy___ysy/article/details/135700140[参考博客]:https://blog.csdn.net/2402_83422357/article/details/139180404戳此切大佬博客https://blog.csdn.net/Morphy_Amo/article/details/122215773https://blog.csdn.net/song_lee/......
  • Go fmt.Print() 格式化
    %v打印结构体%+V打印带有字段的结构体%T打印对象类型%t打印布尔值%d打印整型数,十进制输出,如果d前面有数字,表示控制输出宽度,默认使用空白填充,%05d会在不满5位时填充0%b打印整型数,二进制输出%c打印整型数,字符输出(如果有)%o打印整型数,八进制输出,如果x前面带有#表示带......
  • 错误处理:fmt::Display & std::error::Error
    错误处理为什么要给错误类型(如JsonError)实现fmt::Displaytrait?在Rust中,fmt::Displaytrait允许你定义一个类型如何被格式化为人类可读的字符串。这通常用于错误信息、日志记录或任何其他用户输出。实现fmt::Display需要定义fmt函数,该函数写入特定格式的数据......
  • FWT & FMT
    CF662C首先有一个\(\mathcalO(2^nm)\)的做法。枚举每一行是否反转的状态\(s\),记\(g_i=\min(\operatorname{popcount}(i),n-\operatorname{popcount}(i))\),\(t_i\)表示第\(i\)列的状态。则答案为\(\min_s{\sum_{i=1}^mg_{s\operatorname{xor}t_i}}\)。发现这个东西......
  • 学习笔记:集合幂级数与 FWT
    集合幂级数集合到整数设\(n\)元集\(A=a_1,a_2,\cdots,a_n\),定义\(A\)的幂集\(2^{A}=\{S\midS\subseteqA\}\)到整数集\(\mathbb{Z}\)的映射\(\text{id}\)为:若\(S=\{a_{i_1},a_{i_2},\cdots,a_{i_k}\}\),则\(\text{id}(S)=\sum_{j=1}^{k}2^......
  • FFT/FWT 相关理论自我复习
    下文下标一般从\(0\)开始。卷积:记的数组\(a,b\)在运算\(\circ\)下的卷积\(a\circb=c\),其中\(c_k=\sum\limits_{i\circj=k}a_ib_j\)。直接暴力计算卷积复杂度为\(O(n^2)\),其中\(n\)为数组长度。DFT-IDFT一般快速计算特殊卷积的方法为构造DFT变换:欲构造可逆的......
  • 构造照亮世界——快速沃尔什变换 (FWT)
    博客园我的博客快速沃尔什变换解决的卷积问题快速沃尔什变换(FWT)是解决这样一类卷积问题:\[c_i=\sum_{i=j\odotk}a_jb_k\]其中,\(\odot\)是位运算的一种。举个例子,给定数列\(a,b\),求:\[c_i=\sum_{j\oplusk=i}a_jb_k\]FWT的思想看到FWT的名字,我们可以联想到之前学过......