Liveddd's Blog

愛にできることはまだあるかい

集合幂级数

快速莫比乌斯变换 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.例题

1.CF914G Sum the Fibonacci

模板题。

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]);

//subset
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]);
//xor
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);
//and
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;
}