快速莫比乌斯变换 FMT 与 快速沃尔什变换 FWT。
1.定义
一个长度为 $2^n$ 的序列 $f(i)$,有一个生成函数 $F(x)=\sum_{i=0}^{2^n-1}f(i) x^i$,那么,这个生成函数叫做集合幂级数。
这和集合有什么关系呢?将集合 ${0,1,\cdots,n-1 }$ 的一个子集状态压缩后就对应着原序列的一个下标 $i$。
其卷积形式大多为 $c(i)=\sum_{j\oplus k=i}a(j)*b(k)$,这里的 $\oplus$ 指代某种运算,其大多是位运算。
直接暴力求卷积是 $\mathcal O(4^n)$ 的,考虑更优的做法。
2.快速莫比乌斯变换 FMT
用来求高维前缀和。高维前缀和就是对 $n$ 维数组求前缀和,不过每一维只有 $0,1$ 两个下标。本质上就是子集和 $b(i)=\sum_{j|i=i} a(j)$。暴力枚举子集来求的时间复杂度是 $\mathcal O(3^n)$ 的。
是实际上我们可以对每一维单独来做遍前缀和,容易发现这样做是正确的。于是是时间复杂度 为 $\mathcal O(n2^n)$。
其实这样做法也可以从子集和或者另外的角度来理解。而高维后缀和同理。
Code
1 2 3 4
| for(int i=0;i<n;i++) for(int j=0;j<1<<n;j++) if(j>>i&1) a[j]+=a[j^(1<<i)];
|
3.快速沃尔什变换 FWT
用于求异或卷积,在下面说。
4.卷积
为了让卷积 $c(i)=\sum_{j\oplus k=i}a(i)*b(i)$ 更快计算,我们需要用 FMT/FWT 将 $a,b$ 进行变换,再 $\mathcal O(n)$ 求出变换后的 $c$,再对其进行逆变换就可以求出原本的 $c$。
1.或卷积
求卷积 $c_i=\sum_{j|k=i} a_j*b_k$。
考虑构造出变换 $FMT(c)_x=FMT(a)_x*FMT(b)_x$。
证明如下:
于是先对 $a,b$ 做一次高维前缀和,相乘后做一次逆变换即得到了 $c$。
2.与卷积
和或卷积类似,将高维前缀和变为高维后缀和即可。
3.异或卷积
求卷积 $c_i=\sum_{j\oplus k=i} a_j*b_k$。
我们设对序列 $a$ 变换为 $FWT(a)_x=\sum_{i=0}^{n-1} g(x,i)a_i$。
我们要想使
尝试变换一下:
于是得到:
我们需要构造的式子需要满足上面这个等式,并且能够快速得到 $FWT(a)$。前人的智慧告诉我们应当为 $g(x,i)=(-1)^{|i\odot x|}$,因为有 $(i\oplus j)\odot x=(i\odot x)\oplus (j\odot x)$。所以 FWT 的式子就是:
至于如何快速求出 $FWT(a)$,考虑一个类似于蝶形运算的过程,具体可见代码,这里不再赘述。这样总的时间复杂度为 $\mathcal O(n2^n)$。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| inline void FWT(int n,int *a) { int tot=1<<n; for(int mid=1;mid<tot;mid<<=1) for(int i=0;i<tot;i+=mid<<1) for(int j=0;j<mid;j++) { int x=a[i+j],y=a[i+j+mid]; a[i+j]=x+y,a[i+j+mid]=x-y; } } inline void IFWT(int n,int *a) { int tot=1<<n; for(int mid=1;mid<tot;mid<<=1) for(int i=0;i<tot;i+=mid<<1) for(int j=0;j<mid;j++) { int x=a[i+j],y=a[i+j+mid]; a[i+j]=x+y>>1,a[i+j+mid]=x-y>>1; } }
|
发现此过程和 NTT 非常相似。
4.子集卷积
求 $c_i=\sum_{j\odot k=0,j|k=i} a_j*b_k$。
注意到 $j,k$ 对 $j|k$ 有贡献,当且仅当 $\mid j\mid +\mid k\mid =\mid j|k\mid $。这启发我们按照集合的大小来进行分类。
将原序列扩展到二维为 $A_{x,i}=a_i[|i|=x]$,于是有:
这里我们同时对两边做 FMT,这显然是成立的。我们先处理出 $FMT (A_{|i|}),FMT(B_{|i|})$,求出 $FMT(C_{|x|})$ 后做 $IFMT$ 即可。时间复杂度为 $\mathcal O(n^22^n)$。
Code
1 2 3 4 5 6 7 8 9 10
| for(int i=0;i<=n;i++) FMT(n,a[i]),FMT(n,b[i]); for(int i=0;i<=n;i++) for(int j=0;j<=n-i;j++) { for(int x=0;x<tot;x++) c[i+j][x]=adj(c[i+j][x]+1ll*a[i][x]*b[j][x]%mod); } for(int i=0;i<=n;i++) IFMT(n,c[i]);
|
5.例题
模板题。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
| #include<iostream> #include<cstdio> #include<cstring> #define popcnt __builtin_popcount using namespace std; const int N=17,M=1<<N,mod=1e9+7,inv=mod+1>>1; template<class T> inline void read(T &x) { x=0;int f=0; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=1;ch=getchar();} while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); if(f)x=~x+1; } template<class T,class ...T1> inline void read(T &x,T1 &...x1) { read(x),read(x1...); } int n,m,tot; int f[M],A[M]; int a[N+1][M],b[N+1][M],c[M],d[M],e[M]; inline int adj(int x){return (x>=mod)?x-mod:x;} inline void FMT(int op,int n,int *a) { int tot=1<<n; if(!op) { for(int i=0;i<n;i++) for(int j=0;j<tot;j++) if(j>>i&1) a[j]=adj(a[j]+a[j^(1<<i)]); } else for(int i=0;i<n;i++) for(int j=0;j<tot;j++) if(j>>i&1) a[j^(1<<i)]=adj(a[j]+a[j^(1<<i)]); } inline void IFMT(int op,int n,int *a) { int tot=1<<n; if(!op) { for(int i=0;i<n;i++) for(int j=tot-1;~j;j--) if(j>>i&1) a[j]=adj(a[j]-a[j^(1<<i)]+mod); } else for(int i=0;i<n;i++) for(int j=tot-1;~j;j--) if(j>>i&1) a[j^(1<<i)]=adj(a[j^(1<<i)]-a[j]+mod); } inline void FWT(int n,int *a) { int tot=1<<n; for(int mid=1;mid<tot;mid<<=1) for(int i=0;i<tot;i+=mid<<1) for(int j=0;j<mid;j++) { int x=a[i+j],y=a[i+j+mid]; a[i+j]=adj(x+y),a[i+j+mid]=adj(x-y+mod); } } inline void IFWT(int n,int *a) { int tot=1<<n; for(int mid=1;mid<tot;mid<<=1) for(int i=0;i<tot;i+=mid<<1) for(int j=0;j<mid;j++) { int x=a[i+j],y=a[i+j+mid]; a[i+j]=1ll*adj(x+y)*inv%mod,a[i+j+mid]=1ll*adj(x-y+mod)*inv%mod; } }
int main() { read(m); for(int i=0;i<m;i++) { int x; read(x); A[x]++,tot=max(tot,x); } tot++; while((1<<n)<tot)n++; tot=1<<n; f[0]=0,f[1]=1; for(int i=2;i<tot;i++) f[i]=adj(f[i-1]+f[i-2]);
for(int i=0;i<tot;i++) a[popcnt(i)][i]=A[i]; for(int i=0;i<=n;i++)FMT(0,n,a[i]); for(int i=0;i<=n;i++) for(int j=0;j<=n-i;j++) for(int x=0;x<tot;x++) b[i+j][x]=adj(b[i+j][x]+1ll*a[i][x]*a[j][x]%mod); for(int i=0;i<=n;i++) IFMT(0,n,b[i]); memcpy(c,A,sizeof(c)); FWT(n,c); for(int i=0;i<tot;i++) c[i]=1ll*c[i]*c[i]%mod; IFWT(n,c); for(int i=0;i<tot;i++) d[i]=1ll*b[popcnt(i)][i]*f[i]%mod; memcpy(e,A,sizeof(e)); for(int i=0;i<tot;i++) c[i]=1ll*c[i]*f[i]%mod,e[i]=1ll*e[i]*f[i]%mod; FMT(1,n,c),FMT(1,n,d),FMT(1,n,e); for(int i=0;i<tot;i++) c[i]=1ll*c[i]*d[i]%mod*e[i]%mod; IFMT(1,n,c); int ans=0; for(int i=0;i<n;i++) ans=adj(ans+c[1<<i]); printf("%d\n",ans); return 0; }
|