FWT/FMT 快速莫比乌斯/沃尔什变换 学习笔记

前置知识:高维前缀和,下面前缀和的操作大多都是用高维前缀和来实现的。

大致内容

设有两个长度为 \(2^n\) 的序列 \(A,B\),现在我们要对他们进行一下不同类型的卷积。

(跳过所有推导过程)有模板如下:

struct FWT
{
	int n;
	inline int ksm(int x,int y)
	{
		int ret=1;
		for(;y;y>>=1,x=1ll*x*x%mod) if(y&1) ret=1ll*ret*x%mod;
		return ret;
	}
	inline void bitmul(int *a,int *b)
		{ for(int i=0;i<n;i++) a[i]=1ll*a[i]*b[i]%mod; }
	inline void fwt_or(int *a,int opt)
	{
		for(int p=2;p<=n;p<<=1) for(int i=0;i<n;i+=p) for(int j=0;j<(p>>1);j++)
			(a[i+j+(p>>1)]+=1ll*a[i+j]*opt%mod)%=mod;
	}
	inline void fwt_and(int *a,int opt)
	{
		for(int p=2;p<=n;p<<=1) for(int i=0;i<n;i+=p) for(int j=0;j<(p>>1);j++)
			(a[i+j]+=1ll*a[i+j+(p>>1)]*opt%mod)%=mod;
	}
	inline void fwt_xor(int *a,int opt)
	{
		for(int p=2;p<=n;p<<=1) for(int i=0;i<n;i+=p) for(int j=0;j<(p>>1);j++)
		{
			int x=a[i+j],y=a[i+j+(p>>1)];
			a[i+j]=1ll*(x+y)%mod*opt%mod;
			a[i+j+(p>>1)]=1ll*(x-y+mod)%mod*opt%mod;
		}
	}
}P;

\(\bigstar\texttt{Important}\):由于每个二进制位其实都是等价的,所以在下面的情况中我们都只用考虑 \(n=1\) 的情况。

\(\texttt{or}\) 卷积

\[C_k=\sum_{i~\texttt{or}~j=k}A_i\times B_j \]

\[C_0=A_0\times B_0\\ C_1=A_0\times B_1+A_1\times B_0+A_1\times B_1\\ C_0+C_1=(A_0+A_1)\times (B_0+B_1) \]

受到面式子的启发,考虑将 \(A,B\) 分别进行一次前缀和,每一个对应为乘起来记为 \(C\),再对 \(C\) 做一遍前缀差即可。

memcpy(A,a,sizeof(a)),memcpy(B,b,sizeof(b));
P.fwt_or(A,1),P.fwt_or(B,1),P.bitmul(A,B),P.fwt_or(A,mod-1);
for(int i=0;i<All;i++) printf("%d ",A[i]);
printf("\n");

\(\texttt{and}\) 卷积

\[C_k=\sum_{i~\texttt{and}~j=k}A_i\times B_j \]

\[C_0=A_0\times B_0+A_0\times B_1+A_1\times B_0\\ C_1=A_1\times B_1\\ C_0+C_1=(A_0+A_1)\times (B_0+B_1) \]

\(A,B\) 都做一遍后缀和,按位乘起来记为 \(C\),再对 \(C\) 做一遍后缀差即可。

memcpy(A,a,sizeof(a)),memcpy(B,b,sizeof(b));
P.fwt_and(A,1),P.fwt_and(B,1),P.bitmul(A,B),P.fwt_and(A,mod-1);
for(int i=0;i<All;i++) printf("%d ",A[i]);
printf("\n");

\(\texttt{xor}\) 卷积

\[C_k=\sum_{i~\texttt{xor}~j=k}A_i\times B_j \]

\[C_0=A_0\times B_0+A_1\times B_1\\ C_1=A_0\times B_1+A_1\times B_0\\ \begin{cases} C_0+C_1=(A_0+A_1)\times (B_0+B_1)\\ C_0-C_1=(A_0-A_1)\times (B_0-B_1) \end{cases} \]

那么根据高维前缀和每一位相减过去即可。

memcpy(A,a,sizeof(a)),memcpy(B,b,sizeof(b));
P.fwt_xor(A,1),P.fwt_xor(B,1),P.bitmul(A,B),P.fwt_xor(A,P.ksm(2,mod-2));
for(int i=0;i<All;i++) printf("%d ",A[i]);
printf("\n");

例题

模板题:P4717 【模板】快速莫比乌斯/沃尔什变换 (FMT/FWT)

P6097 【模板】子集卷积

给定两个长度为 \(2^n\) 的序列 \(\{A\},\{B\}\),需要求出一个长度为 \(2^n\) 的序列 \(\{C\}\) 满足:

\[C_k=\sum_{i~\texttt{and}~j=0;i~\texttt{or}~j=k} A_i\times B_j \]

\(n\le 20\),答案对 \(10^9+9\) 取模。

容易发现每次拼接都和元素个数有关,可以将上面的式子转化为:(将每一个二进制数表示为一个集合)

\[\begin{aligned} C_{S}&=\sum_{|P|+|Q|=|S|;P~\texttt{or}~Q=S}A_P\times B_Q\\ &=\sum_{|P|+|Q|=|S|}A_{P}\text{和}B_{Q}\text{的 or 卷积} \end{aligned} \]

接下来需要求两个集合的卷积,我们需要保证两个集合的交为空集。

\(\bigstar\texttt{Hint}\):可以记下二进制位中 \(1\) 的个数,要求一个 \(k\) 位的数和一个 \(i-k\) 位的数或出来一个 \(i\) 位的数,这样可以保证两个数一定没有交集。

\[F(i,S)=\sum_{T\in S,|T|=i}A_T\\ G(i,S)=\sum_{T\in S,|T|=i}B_T\\ H(i,S)=\sum_{j=0}^iF(j,S)\times G(i-j,S) \]

由于 \(H\) 中会出现没有包含所有元素的情况,所以对 \(H\) 做一次 Ifwt_or 即可。

P4221 [WC2018]州区划分

发现 \(n\) 非常小,考虑去暴力状压。

\(dp_{i,s}\) 表示选择了 \(i\) 个州,选择城市的集合为 \(s\) 时的所有方案总和(但后来发现如果这样设是 \(\mathcal{O(n^32^n)}\) 的)。

如果一个州满足要求,它需要满足以下两个条件中至少一个

  • 州联通且每个点度数不全是偶数。
  • 州不连通。

这样转移的时候枚举下一个州的集合,可以列出转移方程:

\[dp_{i,s}\times \left(\dfrac{w_t}{w_s+w_t}\right)^p\rightarrow dp_{i+1,s\cup t} \]

\(dp_i\)\(w\) 当做两个长度为 \(2^n\) 的多项式,我们需要做的就是将他们卷起来:

\[dp_{i+1,z}=\dfrac1{{w_p}^z}\sum_{s\cup t=z,s\cap t=\emptyset}dp_{i,s}\times{w_t}^p \]

那么这就变成了上面子集卷积问题,带上它我们再考虑开始的复杂度问题。

\(\bigstar\texttt{Hint}\):由于我们向超集转移,还需要记录下 popcount,且每次的转移点都让自己 popcount 增加,可以删去第一维记录州的数量,利用 popcount 转移。

枚举 popcount,从小到大转移即可。

CF1034E Little C Loves 3 III

子集卷积的题面,只不过模数变成了 \(4\)\(n\) 变成了 \(21\)!!!

考虑一个构造,令 \(a_i'=a_i\times 4^{\text{pop(i)}},b_i'=b_i\times 4^{\text{pop(i)}}\),直接 fwt_or\(c_i'=\dfrac{c_i}{4^{\text{pop(i)}}}\)

证明:如果 \(i\&j=0\),则 \(\dfrac{4^{\text{pop(j)}}\times 4^{\text{pop(j)}}}{4^{\text{pop(i|j)}}}=1\),有贡献。否则会等于 \(4^x\),由于对 \(4\) 取模会被消掉。

加上一个自然溢出,很高明啊~

posted @ 2022-04-22 17:37  EricQian06  阅读(91)  评论(2编辑  收藏  举报