快速莫比烏斯變換(FMT)


快速莫比烏斯變換(FMT)

原文出處:虞大的博客。此僅作蒟蒻本人復習用~

給定兩個長度為n的序列 \(a_0, a_1, \cdots, a_{n-1}\)\(b_0, b_1, \cdots, b_{n-1}\),你需要求出一個序列\(c_0, c_1, \cdots, c_{n-1}\),其中\(c_k\)滿足:\(c_k = \sum\limits_{i \mid j = k} a_i b_j\)。其中|表示按位或。\(n \leq 10^6\)表示序列長度。

顯然發現\(i∣j=k\)這個條件不怎么好處理,如果我們作一個集合的 "前綴和" ,即令\(P_i = \sum\limits_{j \subseteq i} p_j\)\(i\&j=j\)),那么有:\(C_k = \sum_{k_0 \subseteq k} c_i = \sum_{k_0 \subseteq k} \sum_{i \cup j = k_0} a_i b_j = \sum_{i \cup j \subseteq k} a_i b_j = \left( \sum_{i \subseteq k} a_i \right) \left( \sum_{j \subseteq k} b_j \right) = A_k \cdot B_k\)

所以說我們就把集合並卷積轉化成了兩個“前綴和”集合的\(O(n)\)運算,和FFT差不多。現在的問題就是怎么快速算出這些“前綴和”集合。

我們可以畫一張圖,圖中每一行代表一個\(P_i\)。這一行有哪些格子塗藍,就代表它是哪些\(p_i\)的和:

圖片

如果用動態規划來理解的話,令\(f[i][j]\)表示j的開頭\(2^i\)個數中為權值為1的數的和, 那么轉移顯然就是\(f[i+1][j+2^i]+=f[i][j]\)(j的\(2^i\)位是0)。其中j這一維是可以壓掉的。

如果改變一下你腦海中的求和順序,那么循環內的一次加法,就相當於一個集合的前面2^i個元素的值被加上了。

如果要把\(f\)還原該怎么辦呢?只要把+=改成-=就行了~(不會證,但是真的很好記)

#include <cctype>
#include <cstdio>
using namespace std;

typedef long long LL;
const LL maxn=2e6+5;
LL n, l, bits, a[maxn], b[maxn], c[maxn];

void FMT(LL *f, LL flag){
	for (LL i=0; i<bits; ++i)
		for (LL j=0; j<l; ++j)
			if ((j>>i&1)==0) f[j|1<<i]+=(~flag?f[j]:-f[j]);
}

inline void getint(LL &x){
	char ch; x=0;
	for (; ch=getchar(), !isdigit(ch););
	for (x=ch-48; ch=getchar(), isdigit(ch);)
		x=(x<<3)+(x<<1)+ch-48;
}

int main(){
	getint(n); l=1;
	while (l<n) l<<=1, ++bits;
	for (LL i=0; i<n; ++i) getint(a[i]); FMT(a, 1);
	for (LL i=0; i<n; ++i) getint(b[i]); FMT(b, 1);
	for (LL i=0; i<l; ++i) c[i]=a[i]*b[i]; FMT(c, -1);  
	//坑點1:乘法可能爆LL 坑點二:i要到l 
	for (LL i=0; i<n; ++i) printf("%lld ", c[i]);
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM