zoukankan      html  css  js  c++  java
  • [多项式算法](Part 4)FWT 快速沃尔什变换 学习笔记

    其他多项式算法传送门:

    [多项式算法](Part 1)FFT 快速傅里叶变换 学习笔记

    [多项式算法](Part 2)NTT 快速数论变换 学习笔记

    [多项式算法](Part 3)MTT 任意模数FFT/NTT 学习笔记

    [多项式算法](Part 5)分治FFT 学习笔记

    (4.Extreme-FWT)

    • FWT((Fast Walsh-Hadamard Transform))

      中文名称:快速沃尔什变换

      (Fast Wrong-Answer Transform)


    (Q:)有完没完了?(FWT)又是什么?现在已经能处理任意情况的多项式乘法了,还要这个干什么?

    (A:)我也不想学啊,根本背不下来

    虽然现在我们可以快速求出(C=A*B,C_k=sum_{i+j=k}A_i*B_j)了,但是现在让你求(C=Aoplus B,C_k=sum_{ioplus j=k}A_i*B_j),其中(oplus)代表一个位运算符号,如(|(or),&(and),land(xor)),你该怎么做呢?

    这时就到(FWT)登场了。

    接下来让我们对于(FWT)(3)种形式感性理解分别讨论


    (Part1--FWT(or))

    (A_0,A_1)分别表示长度为(n=2^x)的多项式(A)的前半部分和后半部分。

    首先,给出(FWT(A))的计算方式:

    [FWT(A)=egin{cases} (FWT(A_0),FWT(A_0)+FWT(A_1))&(n>1)\ A&(n=1) end{cases} ]

    其中((A,B))表示两个多项式相连。

    那么(n=1)是显然是对的,边界嘛。

    至于(n>1)的情况如何理解?

    对于(A_0)(A_0)中两个数下标(or)起来一点还在(A_0)中(二进制下最高位为(0),是前半部分),那么就只对前半部分有贡献。

    对于(A_1)(A_1)中两个数,同理只对后半部分有贡献。

    对于(A_0)(A_1)中的两个数,思考(FWT(A)_k)的意义,有:

    [FWT(A)_k=sum_{i|k=k}A_i ]

    因为当(i|k=k,j|k=k)时,有((i|j)|k=k),满足(FWT)的可合并性质。

    那么因为(A_1)下标二进制最高位为(1),所以(or)起来只对后半部分产生贡献。

    贡献就是(A_0)(A_1)的贡献((A_1)已经贡献过自己了,不用再加)。

    那么式子就很明显了。

    同时根据(FWT(A))的意义,容易发现(FWT(A_0+A_1)=FWT(A_0)+FWT(A_1))

    接下来证明(FWT(A|B)=FWT(A)*FWT(B))(保证(or)卷积答案的正确性,要不然(FWT)就没有用了)。

    (n=1)时,性质显然成立

    (n>1)时,:

    [egin{equation} egin{split} FWT(A|B)=&FWT((A|B)_0,(A|B)_1)\ &=FWT(A_0|B_0,A_0|B_1+A_1|B_0+A_1|B_1)\ &=(FWT(A_0|B_0),FWT(A_0|B_0+A_0|B_1+A_1|B_0+A_1|B_1))\ &=(FWT(A_0)*FWT(B_0),FWT(A_0)*FWT(B_0)+FWT(A_0)*FWT(B_1)\ &+FWT(A_1)*FWT(B_0)+FWT(A_1)*FWT(B_1))\ &=(FWT(A_0)*FWT(B_0),(FWT(A_0)+FWT(A_1))*(FWT(B_0)+FWT(B_1)))\ &=(FWT(A_0)*FWT(B_0),FWT(A_0+A_1)*FWT(B_0+B_1)))\ &=(FWT(A_0),FWT(A_0+A_1))*(FWT(B_0),FWT(B_0+B_1))\ &=FWT(A)*FWT(B) end{split} end{equation} ]

    由数学归纳法得知,此性质成立。

    那么(or)(FWT)就很好写了~~代码在后面。

    (Part2--FWT(and))

    (and)(FWT)就和(or)的很类似了。

    因为(A_0)(A_1)最高位不同,那么(and)后只对(A_0)有贡献。

    类似(or)的,可以得到(FWT(A))的计算方式:

    [FWT(A)=egin{cases} (FWT(A_0)+FWT(A_1),FWT(A_1))&(n>0)\ A(n=0) end{cases} ]

    至于证明就不写了,和(or)的类似,写着麻烦

    (Part3--FWT(xor))

    (xor)来了

    说实话,在网上找了许多(Blog),似乎都没有给出构造方法,那么我也不会啊

    你就当是某位神仙找的规律吧

    首先是(FWT(A))的计算方式:

    [FWT(A)=egin{cases} (FWT(A_0)+FWT(A_1),FWT(A_0)-FWT(A_1))&(n>0)\ A(n=0) end{cases} ]

    看着都恶心,这怎么构造出来的啊(QAQ)

    那么根据定义,很容易证明(FWT(Apm B)=FWT(A)pm FWT(B))

    因为(FWT)是一个线性组合,满足以上性质。

    然后是(FWT(Aland B)=FWT(A)*FWT(B))

    这个也可以用数学归纳法证明,详见参考资料


    (Q:)等等……是不是少了什么?(FWT)后怎么变回去呢?

    (A:)这还不简单接下来的过程就是(IFWT)了!

    (Part4--IFWT(or))

    (Emm...)至于(IFWT)呢就很简单了,把变换倒过来即可。

    (这不是废话吗)

    那么对于(or)(IFWT),考虑之前有(FWT)的方程:

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

    也就是:

    [FWT(A)_0=FWT(A_0),FWT(A)_1=FWT(A_0)+FWT(A_1) ]

    [FWT(A_0)=FWT(A)_0,FWT(A_1)=FWT(A_0)-FWT(A)_1=FWT(A)_0-FWT(A)_1 ]

    此时定义(IFWT(A)_0=FWT(A_0),IFWT(A)_1=FWT(A_1)) ,也就有:

    [IFWT(A)=egin{cases} (IFWT(A_0),IFWT(A_0)-IFWT(A_1))&(n>0)\ A&(n=0) end{cases} ]

    (En...)真简单

    (Part5--IFWT(and))

    类似(or)(IFWT),可以直接得到:

    [IFWT(A)=egin{cases} (IFWT(A_0)-IFWT(A_1),IFWT(A_1))&(n>0)\ A(n=0) end{cases} ]

    过程类似,这里就不多赘述。。

    (Part6--IFWT(xor))

    (xor)(IFWT)出人意料地一样简单。

    由定义得:

    [egin{cases} FWT(A)_0=FWT(A_0)+FWT(A_1)\ FWT(A)_1=FWT(A_0)-FWT(A_1) end{cases} ]

    解方程就简单了。

    最后有:

    [IFWT(A)=egin{cases} (frac{IFWT(A_0)+IFWT(A_1)}2,frac{IFWT(A_0)-IFWT(A_1)}2)&(n>0)\ A&(n=0) end{cases} ]

    终于完了


    那么接下来就是看图写话看定义写代码过程了:

    这里为了节省代码量把(6)个函数写一起了(因为框架大致类似)。

    P4717 【模板】快速沃尔什变换

    代码:

    #include <cstdio>
    #include <cstring>
    typedef long long ll;
    
    const int Mod=998244353,Inv2=(Mod+1)>>1;//Inv2 2在mod998244353下的逆元
    int n,a[1<<17],b[1<<17],as[1<<17],bs[1<<17];
    
    void FWT(int *A,int op,int t)
    //op [1/-1][FWT/IFWT]
    //t [1,2,3][or/and/xor]
    {
        for(int i=2;i<=n;i<<=1)//i 区间长度
            for(int j=0,m=i>>1;j<n;j+=i)//j 区间左端 m 区间大小一半
                for(int k=0;k<m;++k)//k 正在算第几个数
                    if(t==1)A[j+m+k]=((ll)A[j+m+k]+A[j+k]*op+Mod)%Mod;
                    else if(t==2)A[j+k]=((ll)A[j+k]+A[j+m+k]*op+Mod)%Mod;
                    else
                    {
                        int A0=A[j+k],A1=A[j+m+k];
                        A[j+k]=(ll)(A0+A1)*(op==1?1:Inv2)%Mod;
                        A[j+m+k]=(ll)(A0-A1+Mod)*(op==1?1:Inv2)%Mod;
                    }
    }
    
    int main()
    {
        scanf("%d",&n),n=1<<n;
        for(int i=0;i<n;++i)scanf("%d",&as[i]);
        for(int i=0;i<n;++i)scanf("%d",&bs[i]);
        for(int t=1;t<=3;++t)//分别计算or/and/xor
        {
            memcpy(a,as,sizeof(int)*n);
            memcpy(b,bs,sizeof(int)*n);
            FWT(a,1,t),FWT(b,1,t);
            for(int i=0;i<n;++i)a[i]=a[i]*1LL*b[i]%Mod;
            FWT(a,-1,t);
            for(int i=0;i<n;++i)printf("%d%c",a[i],i==n-1?'
    ':' ');
        }
        return 0;
    }
    

    代码应该很好懂,就不解释了。

    我只能说:(FWT)真好背真好写。

    参考资料:((Dalao TQL)

    FWT快速沃尔什变换学习笔记-小蒟蒻yyb

    关于快速沃尔什变换(FWT)的一点学习和思考-ACMLCZH

  • 相关阅读:
    HTML5游戏参考资料
    Xcode 4.2 编译 ios5.1
    Const,Const函数,Const变量,函数后面的Const (zz)
    AndroidJNI 通过C++调用JAVA
    android adb shell 命令大全
    《Programming WPF》翻译 目录
    《Programming WPF》翻译 第3章 1.什么是控件
    CLR笔记目录
    《Programming WPF》翻译 第3章 3.内嵌控件
    《Programming WPF》翻译 第3章 2.处理输入
  • 原文地址:https://www.cnblogs.com/LanrTabe/p/11305626.html
Copyright © 2011-2022 走看看