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


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

    学的时候比较匆忙,于是就学一个 \(\texttt{or,and,xor}\) 卷积跑路。

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

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

    大致内容

    设有两个长度为 \(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");
    

    例题

    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} \]

    接下来需要求两个集合的卷积,设:

    \[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 即可。

  • 相关阅读:
    阿里代码检查工具
    SpringCloud 服务通信方法
    Apache ab简单使用
    H5 webSocket使用
    Java list去重 取重
    解决 spring boot Failed to decode downloaded font
    Pycharm设置快捷方式
    环境变量与文件的查找
    Linux基础
    VIM中文乱码解决
  • 原文地址:https://www.cnblogs.com/EricQian/p/16178853.html
Copyright © 2020-2023  润新知