FWT快速沃爾什變換——基於樸素數學原理的卷積算法

這是我的第一篇學習筆記,如有差錯,請海涵...


目錄

引子

卷積形式

算法流程

OR卷積

AND卷積

XOR卷積

模板


引子

首先,考慮這是兔子

數一數,會發現你有一隻兔子,現在,我再給你一隻兔子

再數一數,會發現什麼?沒錯,你有兩隻兔子,也就是說,1+1=2

這就是算數的基本原理了,聰明的你懂了嗎?

好,我們可以學FWT了..


卷積形式

我們回憶一下多項式乘法的式子:

C_{i}=\sum_{j,k}A_j*B_k\;(j+k==i)

這個可以用FFT或NTT優化到O(nlogn)求出每一個Ci,但不是本章的重點,只是引出卷積的概念:

C_{i}=\sum_{j,k}A_j*B_k\;(f(j,k)==i)

而FWT主要是解決以下三種卷積形式:

C_{i}=\sum_{j,k}A_j*B_k\;(j \or\;k==i)

C_{i}=\sum_{j,k}A_j*B_k\;(j\and\;k==i)

C_{i}=\sum_{j,k}A_j*B_k\;(j\;xor\;k==i)


算法流程

卷積的算法原理就是把一個數列快速轉換成另一種數列,然後每一位元素之間就可以直接單獨相乘計算,最後再把答案數列快速轉換回來。

FFT體現這個原理的方式就是把多項式轉換成點值表達式,然後由於每個點的橫座標相同,縱座標直接乘起來就得到最終的點值表達式,最後把答案的多項式表達通過點值表達式解出來。

那FWT怎麼做呢?

首先就是數列長度的問題,我們知道,多項式乘法最終會得到一個長爲lenA+lenB-1的多項式,而考慮位運算的卷積——很容易想出,最終的數列長度一定是2^n,n是A、B大小轉換爲二進制後的數的最大位數。

我們設數列A的轉換數列是DWT(A),轉換後的數列A的原數列是IDWT(A)

既然它是位運算,那麼我們就按位分治

我們從二進制最高位考慮起,每次把當前位爲0或1的元素分開成兩個數列,很顯然,由於數列長度爲2^n,直接每次從中間分開就好了,

那麼

DWT(A)=\begin{cases} \{DWT(aA_0+bA_1),DWT(cA_0+dA_1)\} & (len_A>1)\\ A & (len_A=1) \end{cases}

這裏的“{  ,  }”是把兩個數列前後拼一起,A+B是把兩個數列排頭對齊,然後每一位相加。

具體的係數a,b,c,d是怎麼樣,or , and 和 xor 的情況是不一樣的。

OR卷積

因爲是按位或,所以當前位爲1的對0沒有影響,而0的元素都要對1有影響(0可以 | 1變成1,但是1怎麼 | 都不會變成0),於是它的DWT就是這樣

DWT(A)=\begin{cases} \{DWT(A_0),DWT(A_0+A_1)\} & (len_A>1)\\ A & (len_A=1) \end{cases}

這樣DWT(A)[i]就相當於下標按位或 i 後等於 i 的元素和,轉換回去剛好就把當前位爲1的減去爲0的就行,即

IDWT(A)=\begin{cases} \{IDWT(A_0),IDWT(A_1)-IDWT(A_0)\} & (len_A>1)\\ A & (len_A=1) \end{cases}

這就是DWT的逆運算形式吧。

ps:巧合的是,這個玩意其實也是快速莫比烏斯變換FMT,兩個是一樣的,完全沒有區別,也就是說DWT(A)[i]其實也是i的所有子集元素和。

舉個栗子

A=\{ a_0,a_1\},B=\{ b_0,b_1\}

\Rightarrow DWT(A)=\{ a_0,a_0+a_1\}\;,\;DWT(B)=\{ b_0,b_0+b_1\}

\begin{align*} \Rightarrow DWT(C) &= \{ a_0b_0,(a_0+a_1)(b_0+b_1)\}\\ &= \{ a_0b_0,a_0b_0+a_0b_1+a_1b_0+a_1b_1\}\\ &= \{ a_0b_0,a_0b_0+(a_0b_1+a_1b_0+a_1b_1)\} \end{align*}

\Rightarrow C=\{ a_0b_0,a_0b_1+a_1b_0+a_1b_1\}

解決了!

AND卷積

和or很相像

因爲是按位與,所以當前位爲0的對1沒有影響,而1的元素都要對0有影響(1可以&0變成0,但是0怎麼&都不會變成1),於是它的DWT就是這樣

DWT(A)=\begin{cases} \{DWT(A_0+A_1),DWT(A_1)\} & (len_A>1)\\ A & (len_A=1) \end{cases}

這樣DWT(A)[i]就相當於下標按位與 i 後等於 i 的元素和,轉換回去剛好就把當前位爲0的減去爲1的就行,即

IDWT(A)=\begin{cases} \{IDWT(A_0)-IDWT(A_1),IDWT(A_1)\} & (len_A>1)\\ A & (len_A=1) \end{cases}

這又剛好是DWT的逆運算了。

再舉個栗子

A=\{ a_0,a_1\},B=\{ b_0,b_1\}

\Rightarrow DWT(A)=\{ a_0+a_1,a_1\}\;,\;DWT(B)=\{ b_0+b_1,b_1\}

\begin{align*} \Rightarrow DWT(C) &= \{(a_0+a_1)(b_0+b_1),a_1b_1\}\\ &= \{ a_0b_0+a_0b_1+a_1b_0+a_1b_1,a_1b_1\}\\ &= \{ (a_0b_0+a_0b_1+a_1b_0)+a_1b_1,a_1b_1\} \end{align*}

\Rightarrow C=\{ a_0b_0+a_0b_1+a_1b_0,a_1b_1\}

XOR卷積

這個就比較特殊了

我們從栗子裏會發現,對於異或,我們最後其實要把 a0b0+a1b1 和 a1b0+a0b1 單獨刨出來。(這不是廢話!)

那麼在DWT(C)中,a0b0的係數要和a1b1一樣,a1b0的係數要和a0b1一樣

……

於是它的DWT就是這樣!:

DWT(A)=\begin{cases} \{DWT(A_0+A_1),DWT(A_0-A_1)\} & (len_A>1)\\ A & (len_A=1) \end{cases}

這樣DWT(C)就符合條件了,它的IDWT是

IDWT(A)=\begin{cases} \{\frac{IDWT(A_0)+IDWT(A_1)}{2},\frac{IDWT(A_0)-IDWT(A_1)}{2}\} & (len_A>1)\\ A & (len_A=1) \end{cases}

這個得看栗子才明白

A=\{ a_0,a_1\},B=\{ b_0,b_1\}

\Rightarrow DWT(A)=\{ a_0+a_1,a_0-a_1\}\;,\;DWT(B)=\{ b_0+b_1,b_0-b_1\}

\begin{align*} \Rightarrow DWT(C) &= \{(a_0+a_1)(b_0+b_1),(a_0-a_1)(b_0-b_1)\}\\ &= \{ a_0b_0+a_0b_1+a_1b_0+a_1b_1,a_0b_0-a_0b_1-a_1b_0+a_1b_1\}\\ &= \{ (a_0b_0+a_1b_1)+(a_1b_0+a_0b_1),(a_0b_0+a_1b_1)-(a_1b_0+a_0b_1)\} \end{align*}

\Rightarrow C=\{ a_0b_0+a_1b_1,a_1b_0+a_0b_1\}


模板

下面是非遞歸版本的DWT以及IDWT,m爲數列長度(m=2^n

inline void DWTOR(int *s,int m) {
	for(int k = m;k > 1;k >>= 1) {
		for(int i = 0;i < m;i += k) {
			for(int j = i+(k>>1);j < i+k;j ++) {
				int s0 = s[j-(k>>1)],s1 = s[j];
				s[j] = qm((s0 +0ll+ s1) , zxy);
			}
		}
	}
	return ;
}
inline void IDWTOR(int *s,int m) {
	for(int k = 2;k <= m;k <<= 1) {
		for(int i = 0;i < m;i += k) {
			for(int j = i+(k>>1);j < i+k;j ++) {
				int s0 = s[j-(k>>1)],s1 = s[j];
				s[j] = qm((s1 +0ll+ zxy - s0) , zxy);
			}
		}
	}
	return ;
}


inline void DWTAND(int *s,int m) {
	for(int k = m;k > 1;k >>= 1) {
		for(int i = 0;i < m;i += k) {
			for(int j = i+(k>>1);j < i+k;j ++) {
				LL s0 = s[j-(k>>1)],s1 = s[j];
				s[j-(k>>1)] = qm((s0 +0ll+ s1) , zxy);
			}
		}
	}
	return ;
}
inline void IDWTAND(int *s,int m) {
	for(int k = 2;k <= m;k <<= 1) {
		for(int i = 0;i < m;i += k) {
			for(int j = i+(k>>1);j < i+k;j ++) {
				int s0 = s[j-(k>>1)],s1 = s[j];
				s[j-(k>>1)] = qm((s0 +0ll+ zxy - s1) , zxy);
			}
		}
	}
	return ;
}


inline void DWTXOR(int *s,int m) {
	for(int k = m;k > 1;k >>= 1) {
		for(int i = 0;i < m;i += k) {
			for(int j = i+(k>>1);j < i+k;j ++) {
				int s0 = s[j-(k>>1)],s1 = s[j];
				s[j] = qm((s0 +0ll+ zxy - s1) , zxy);
				s[j-(k>>1)] = qm((s0 +0ll+ s1) , zxy);
			}
		}
	}
	return ;
}
inline void IDWTXOR(int *s,int m) {
	for(int k = 2;k <= m;k <<= 1) {
		for(int i = 0;i < m;i += k) {
			for(int j = i+(k>>1);j < i+k;j ++) {
				int s0 = s[j-(k>>1)],s1 = s[j];
				s[j-(k>>1)] = qm((s0 +0ll+ s1) , zxy) *1ll* inv2 % zxy;
				s[j] = qm((s0 +0ll+ zxy - s1) , zxy) *1ll* inv2 % zxy;
			}
		}
	}
	return ;
}

 FWT就到這裏了,大家都懂了吧 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章