多项式算法3:FWT\FMT(快速沃尔什变换\快速莫比乌斯变换)

多项式算法3:FWT\FMT(快速沃尔什变换\快速莫比乌斯变换)

前言

对于一个序列,将其中的元素一一映射到一个多项式函数的系数上,这个多项式函数便叫做该序列的生成函数
对于序列 f 0 , f 1 , ⋯   , f n f_0,f_1,\cdots,f_n f0,f1,,fn f ( x ) = ∑ i = 0 n f i x i f(x)= \sum_{i=0}^n f_ix^i f(x)=i=0nfixi为其生成函数,我们一般叫他们多项式。
卷积是通过两个函数 f f f g g g生成第三个函数的一种数学运算,其本质是一种特殊的积分变换。
当多项式卷积为 C k = ∑ i + j = k A i × B j C_k=\sum_{i+j=k} A_i \times B_j Ck=i+j=kAi×Bj
我们可以用FFT,NTT来做。
而当多项式卷积为(这里 ⊕ \oplus 指代异或): C k = ∑ i ∣ j = k A i × B j C_k=\sum_{i|j=k} A_i \times B_j Ck=ij=kAi×Bj C k = ∑ i & j = k A i × B j C_k=\sum_{i\&j=k} A_i \times B_j Ck=i&j=kAi×Bj C k = ∑ i ⊕ j = k A i × B j C_k=\sum_{i \oplus j=k} A_i \times B_j Ck=ij=kAi×Bj我们的FFT,NTT就不能派上用场了,这就要用到下面讲的FWT\FMT。而一般来讲,FMT(快速莫比乌斯变换)处理OR,AND问题,而FWT(快速沃尔什变换)是处理XOR问题的。

正文

OR运算的FMT

我们不妨先回忆一下FFT的思路:
先对于一个多项式求出它在带入若干单位根后的点值表示法,然后把点值表示法下的多项式乘起来,然后再把点值表示法变成多项式表示法。
FMT的思路与此类似。
不过,由于这三类与位运算关联的多项式卷积有其特殊的性质,我们不再代入若干指定的值,而是先进行一些变换。
我们不妨进行如下定义:
要求 c k = ∑ i ∣ j = k a i × b j c_k=\sum_{i|j=k} a_i \times b_j ck=ij=kai×bj F M T ( f n ) = ∑ i ∣ n = n f i FMT(f_n)= \sum_{i | n=n} f_i FMT(fn)=in=nfi不难发现
F M T ( a t ) × F M T ( b t ) = ( ∑ i ∣ t = t a i ) × ( ∑ j ∣ t = t b j ) = ∑ i ∣ t = t ∑ j ∣ t = t ( a i × b j ) = ∑ ( i ∣ j ) ∣ t = t ( a i × b j ) = F M T ( c t ) FMT(a_t) \times FMT(b_t) \\ =(\sum_{i|t=t}a_i) \times (\sum_{j|t=t}b_j) \\ = \sum_{i|t=t} \sum_{j|t=t} (a_i \times b_j) \\ = \sum_{(i|j)|t=t}(a_i \times b_j) \\ =FMT(c_t) FMT(at)×FMT(bt)=(it=tai)×(jt=tbj)=it=tjt=t(ai×bj)=(ij)t=t(ai×bj)=FMT(ct)
由此可得 F M T ( a t ) × F M T ( b t ) = F M T ( c t ) FMT(a_t) \times FMT(b_t)=FMT(c_t) FMT(at)×FMT(bt)=FMT(ct),这虽然不是真正的点值表示法,却在多项式乘法中发挥着同样的作用,我们只要找到快速算出 F M T ( f n ) FMT(f_n) FMT(fn)和逆运算 F M T ( f n ) FMT(f_n) FMT(fn)的方法即可。
我们先拿出以前FFT用过的经典三重循环,看看能不能得到什么启示。

for ( int i = 2 ; i <= len ; i <<= 1 ) {//枚举步长,从递归的下面往上走 
		for ( int j = 0 ; j <= len - 1 ; j += i ) {//走一遍步长 
			for ( int k = j ; k < j + i / 2 ; ++k ) {//枚举每块区间内的每一个元素
				int u = f[k];
				int v = f[k + i / 2];
			}
		}
	}

通过观察不难发现,假设在 i i i的某一次取值中 2 t = i ( k ⩾ 1 ) 2^t=i(k\geqslant1) 2t=i(k1),则 j j j的那层循环只改变 u u u v v v下标数 k k k k + i 2 k+\frac{i}{2} k+2i的二进制表示中第 t t t位以及后面的数,而 k k k的那层循环开始的数为 i i i的倍数,二进制表示下后面显然有至少 t t t个零,而 k k k最多循环出 2 t − 1 2^{t-1} 2t1次,还有一次是后面至少 t t t个零的数,所以这层循环刚好不影响到第 t − 1 t-1 t1位,因而这式子中 k ∣ ( k + i 2 ) = k + i 2 k|(k+\frac{i}{2})=k+\frac{i}{2} k(k+2i)=k+2i k + i 2 k+\frac{i}{2} k+2i刚好比 k k k多出个 2 t − 1 2^{t-1} 2t1。通过背包思想不难求出FMT。
至于逆运算?通过子集和反推,只要把加的减回去就可以了(至于正确性的证明?我不会QWQ )。
代码如下:

void FMTor( int f[] , int len , int on ) {//on=1\-1切换模式
	for ( int i = 2 ; i <= len ; i <<= 1 ) {//枚举步长,从递归的下面往上走 
		for ( int j = 0 ; j <= len - 1 ; j += i ) {//走一遍步长 
			for ( int k = j ; k < j + i / 2 ; ++k ) {//枚举每块区间内的每一个元素
				f[k + i / 2] += f[k] * on;
			}
		}
	}
	return;
}

时间复杂度和FFT一样,为 Θ ( n log ⁡ n ) \varTheta(n\log n) Θ(nlogn)
整体思路:
a i → F M T ( a i ) b i → F M T ( b i ) F M T ( c i ) = F M T ( a i ) × F M T ( b i ) F M T ( c i ) → c i a_i \to FMT(a_i) \\ b_i \to FMT(b_i) \\ FMT(c_i)=FMT(a_i) \times FMT(b_i) \\ FMT(c_i) \to c_i aiFMT(ai)biFMT(bi)FMT(ci)=FMT(ai)×FMT(bi)FMT(ci)ci

AND运算的FMT

和OR很多地方相似。
要求 c k = ∑ i & j = k a i × b j c_k=\sum_{i \& j=k} a_i \times b_j ck=i&j=kai×bj F M T ( f n ) = ∑ i & n = n f i FMT(f_n)= \sum_{i \& n=n} f_i FMT(fn)=i&n=nfi不难发现
F M T ( a t ) × F M T ( b t ) = ( ∑ i & t = t a i ) × ( ∑ j & t = t b j ) = ∑ i & t = t ∑ j & t = t ( a i × b j ) = ∑ ( i & j ) & t = t ( a i × b j ) = F M T ( c t ) FMT(a_t) \times FMT(b_t) \\ =(\sum_{i \& t=t}a_i) \times (\sum_{j \& t=t}b_j) \\ = \sum_{i \& t=t} \sum_{j \& t=t} (a_i \times b_j) \\ = \sum_{(i \& j) \& t=t}(a_i \times b_j) \\ =FMT(c_t) FMT(at)×FMT(bt)=(i&t=tai)×(j&t=tbj)=i&t=tj&t=t(ai×bj)=(i&j)&t=t(ai×bj)=FMT(ct)
易得 i ∣ j = i i|j=i ij=i i i i j j j满足 i & j = j i \&j=j i&j=j。加给 k + i 2 k+\frac{i}{2} k+2i变成加给 k k k,所以我们只要把上面的代码微改即可。
代码如下:

void FMTand( int f[] , int len , int on ) {
	for ( int i = 2 ; i <= len ; i <<= 1 ) {//枚举步长,从递归的下面往上走 
		for ( int j = 0 ; j <= len - 1 ; j += i ) {//走一遍步长 
			for ( int k = j ; k < j + i / 2 ; ++k ) {//枚举每块区间内的每一个元素
				f[k] += f[k + i / 2] * on;
			}
		}
	}
	return;
}

XOR运算的FMT

xor运算的变换不好想到。我们先设 p o p c o u n t ( x ) popcount(x) popcount(x) x x x的二进制表示下 1 1 1的个数。
不难发现异或运算满足 p o p c o u n t ( a ) + p o p c o u n t ( b ) ≡ p o p c o u n t ( a ⊕ b ) ( m o d    2 ) popcount(a)+popcount(b) \equiv popcount(a \oplus b) (\mod 2) popcount(a)+popcount(b)popcount(ab)(mod2)
我们设 F W T ( f t ) = ∑ i = 0 n ( − 1 ) ∣ i & t ∣ f i FWT(f_t)=\sum_{i=0}^n(-1)^{|i \& t|}f_i FWT(ft)=i=0n(1)i&tfi,其中设 ∣ x ∣ |x| x表示 p o p c o u n t ( x ) popcount(x) popcount(x)
不难发现
F W T ( a t ) × F W T ( b t ) = ( ∑ i = 0 n ( − 1 ) ∣ i & t ∣ a i ) × ( ∑ j = 0 n ( − 1 ) ∣ j & t ∣ b j ) = ∑ i = 0 n ∑ j = 0 n ( − 1 ) ∣ i & t ∣ ( − 1 ) ∣ j & t ∣ a i b j = ∑ i = 0 n ∑ j = 0 n ( − 1 ) ∣ i & t ∣ + ∣ j & t ∣ a i b j FWT(a_t) \times FWT(b_t) \\ =(\sum_{i=0}^n(-1)^{|i \& t|}a_i ) \times (\sum_{j=0}^n(-1)^{|j \& t|}b_j ) \\ = \sum_{i=0}^n\sum_{j=0}^n (-1)^{|i \& t|}(-1)^{|j \& t|}a_i b_j \\ = \sum_{i=0}^n\sum_{j=0}^n (-1)^{|i \& t| + |j \& t|}a_i b_j FWT(at)×FWT(bt)=(i=0n(1)i&tai)×(j=0n(1)j&tbj)=i=0nj=0n(1)i&t(1)j&taibj=i=0nj=0n(1)i&t+j&taibj
由于异或满足相同为0不同为1,所以异或中两两消掉的1不改变 − 1 -1 1的指数的奇偶性。即 ∣ i & t ∣ + ∣ j & t ∣ = ∣ ( i ⊕ j ) & t ∣ |i \& t| + |j \& t|=|(i \oplus j) \& t| i&t+j&t=(ij)&t
= ∑ i = 0 n ∑ j = 0 n ( − 1 ) ∣ ( i ⊕ j ) & t ∣ a i b j = F W T ( c t ) = \sum_{i=0}^n\sum_{j=0}^n (-1)^{|(i \oplus j) \& t|}a_i b_j \\ =FWT(c_t) =i=0nj=0n(1)(ij)&taibj=FWT(ct)
FWT的计算要考虑分治,设
C 0 = ( c 0 , c 1 , ⋯   , c n 2 − 1 ) C_0=(c_0,c_1,\cdots,c_{\frac{n}{2}-1}) C0=(c0,c1,,c2n1)
C 1 = ( c n 2 , c n 2 + 1 , ⋯   , c n − 1 ) C_1=(c_{\frac{n}{2}},c_{\frac{n}{2} + 1},\cdots,c_{n- 1}) C1=(c2n,c2n+1,,cn1)
其中 F M T ( C 0 ) , F M T ( C 1 ) FMT(C_0),FMT(C_1) FMT(C0)FMT(C1)表示迭代过后能代表这些项的 F M T FMT FMT之和的项。
k < n 2 k \lt \frac{n}{2} k<2n,有
F W T ( c k ) = ∑ i = 0 n 2 − 1 ( − 1 ) ∣ i & k ∣ c i + ∑ i = 0 n 2 − 1 ( − 1 ) ∣ i & k ∣ c i + n 2 = F M T ( C 0 ) + F M T ( C 1 ) FWT(c_k)=\sum_{i=0}^{\frac{n}{2}-1}(-1)^{|i \& k|}c_i + \sum_{i=0}^{\frac{n}{2}-1}(-1)^{|i \& k|}c_{i+\frac{n}{2}}=FMT(C_0)+FMT(C_1) FWT(ck)=i=02n1(1)i&kci+i=02n1(1)i&kci+2n=FMT(C0)+FMT(C1)
F W T ( c k + n 2 ) = ∑ i = 0 n 2 − 1 ( − 1 ) ∣ i & k ∣ c i − ∑ i = 0 n 2 − 1 ( − 1 ) ∣ i & k ∣ c i + n 2 = F M T ( C 0 ) − F M T ( C 1 ) FWT(c_k + \frac{n}{2})=\sum_{i=0}^{\frac{n}{2}-1}(-1)^{|i \& k|}c_i - \sum_{i=0}^{\frac{n}{2}-1}(-1)^{|i \& k|}c_{i+\frac{n}{2}}=FMT(C_0)-FMT(C_1) FWT(ck+2n)=i=02n1(1)i&kcii=02n1(1)i&kci+2n=FMT(C0)FMT(C1)
这里有负一是因为强行给 − 1 -1 1降了一次幂。
可得代码:

void FWTxor( int f[] , int len ) {
	for ( int i = 2 ; i <= len ; i <<= 1 ) {//枚举步长,从递归的下面往上走 
		for ( int j = 0 ; j <= len - 1 ; j += i ) {//走一遍步长 
			for ( int k = j ; k < j + i / 2 ; ++k ) {//枚举每块区间内的每一个元素
				int u = f[k];
				int v = f[k + i / 2];
				f[k] = u + v;
				f[k + i / 2] = u - v;
			}
		}
	}
	return;
}

逆运算也不难推出 x = u + v , y = u − v → u = x + y 2 , v = x − y 2 x=u+v,y=u-v \to u = \frac{x + y}{2},v = \frac{x - y}{2} x=u+v,y=uvu=2x+y,v=2xy
结合起来:

void FWTxor( int f[] , int len , int on ) {
	for ( int i = 2 ; i <= len ; i <<= 1 ) {//枚举步长,从递归的下面往上走 
		for ( int j = 0 ; j <= len - 1 ; j += i ) {//走一遍步长 
			for ( int k = j ; k < j + i / 2 ; ++k ) {//枚举每块区间内的每一个元素
				int u = f[k];
				int v = f[k + i / 2];
				f[k] = u + v;
				f[k + i / 2] = u - v;
				if( on == -1 ) {
					f[k] /= 2;
					f[k + i / 2] /= 2;//一般是题目给模数然后乘逆元 
				}
			}
		}
	}
	return;
}

三个综合起来:
模板题

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define ll long long
using namespace std;
const int mod = 998244353;
int n;
ll inv2;
const int N = 1 << 17 | 1;
inline ll qw( ll a , ll b ) {
	ll ans = 1;
	while ( b ) {
		if( b & 1 ) {
			ans = ( ans * a ) % mod;
		}
		a = ( a * a ) % mod;
		b >>= 1;
	}
	return ans;
}
void FMTor( ll f[] , int len , int on ) {//on=1\-1切换模式
	for ( int i = 2 ; i <= len ; i <<= 1 ) {//枚举步长,从递归的下面往上走 
		for ( int j = 0 ; j <= len - 1 ; j += i ) {//走一遍步长 
			for ( int k = j ; k < j + i / 2 ; ++k ) {//枚举每块区间内的每一个元素
				f[k + i / 2] =( ( f[k + i / 2] + f[k] * on ) % mod + mod ) % mod;
			}
		}
	}
	return;
}
void FMTand( ll f[] , int len , int on ) {
	for ( int i = 2 ; i <= len ; i <<= 1 ) {//枚举步长,从递归的下面往上走 
		for ( int j = 0 ; j <= len - 1 ; j += i ) {//走一遍步长 
			for ( int k = j ; k < j + i / 2 ; ++k ) {//枚举每块区间内的每一个元素
				f[k] =( ( f[k] + f[k + i / 2] * on ) % mod + mod ) % mod;
			}
		}
	}
	return;
}
void FWTxor( ll f[] , int len , int on ) {
	for ( int i = 2 ; i <= len ; i <<= 1 ) {//枚举步长,从递归的下面往上走 
		for ( int j = 0 ; j <= len - 1 ; j += i ) {//走一遍步长 
			for ( int k = j ; k < j + i / 2 ; ++k ) {//枚举每块区间内的每一个元素
				ll u = f[k];
				ll v = f[k + i / 2];
				f[k] = ( ( u + v ) % mod + mod ) % mod;
				f[k + i / 2] = ( ( u - v ) % mod + mod ) % mod;
				if( on == -1 ) {
					f[k] = ( ( 1ll * f[k] * inv2 ) % mod + mod ) % mod;
					f[k + i / 2] = ( ( 1ll * f[k + i / 2] * inv2 ) % mod + mod ) % mod;//一般是题目给模数然后乘逆元 
				}
			}
		}
	}
	return;
}
ll a[N] , b[N] , ta[N] , tb[N];
int main() {
	inv2 = qw( 2ll , mod - 2 );
	scanf("%d",&n);
	int len = qw( 2 , n );
	for ( int i = 0 ; i < len ; ++i ) {
		scanf("%lld",&a[i]);
	}
	for ( int i = 0 ; i < len ; ++i ) {
		scanf("%lld",&b[i]);
	}
	for ( int i = 0 ; i < len ; ++i ) {
		ta[i] = a[i];
	}
	for ( int i = 0 ; i < len ; ++i ) {
		tb[i] = b[i];
	}
	FMTor( a , len , 1 );
	FMTor( b , len , 1 );
	for ( int i = 0 ; i < len ; ++i ) {
		a[i] = ( ( a[i] * b[i] ) % mod + mod ) % mod;
	}
	FMTor( a , len , -1 );
	for ( int i = 0 ; i < len ; ++i ) {
		printf("%lld ",(a[i] % mod + mod) % mod);
	}
	puts("");
	for ( int i = 0 ; i < len ; ++i ) {
		a[i] = ta[i];
	}
	for ( int i = 0 ; i < len ; ++i ) {
		b[i] = tb[i];
	}
	FMTand( a , len , 1 );
	FMTand( b , len , 1 );
	for ( int i = 0 ; i < len ; ++i ) {
		a[i] = ( ( a[i] * b[i] ) % mod + mod ) % mod;
	}
	FMTand( a , len , -1 );
	for ( int i = 0 ; i < len ; ++i ) {
		printf("%lld ",(a[i] % mod + mod) % mod);
	}
	puts("");
	for ( int i = 0 ; i < len ; ++i ) {
		a[i] = ta[i];
	}
	for ( int i = 0 ; i < len ; ++i ) {
		b[i] = tb[i];
	}
	FWTxor( a , len , 1 );
	FWTxor( b , len , 1 );
	for ( int i = 0 ; i < len ; ++i ) {
		a[i] = ( ( a[i] * b[i] ) % mod + mod ) % mod;
	}
	FWTxor( a , len , -1 );
	for ( int i = 0 ; i < len ; ++i ) {
		printf("%lld ",(a[i] % mod + mod) % mod);
	}
	puts("");
	return 0;
} 
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值