zoukankan      html  css  js  c++  java
  • [FWT] 时隔一年再回首FWT(快速沃尔什变换),我终于不再是个门外汉

    时隔一年再回首FWT,我似乎有了新理解??
    添加了原理的推导,以前就只有模板...

    引入

    在这里插入图片描述

    第一次学习的时候,因为连\(FFT\)都没学懂
    所以当时学\(FWT\)加之是在家里网课
    就直接掉线无法连回——一直都是背板子,是完全没学懂啊
    最近重新学习了一遍,就马上来完善一下只有模板的博客

    学了\(FFT\),我们知道是可以用来算多项式乘法的

    \[h(x)=\sum_{i+j=x}f(i)\times g(j) \]

    但是可不可以算其他的,改变\(i,j\)之间的关系限制呢??

    \[\left\{ \begin{aligned} h(x)=\sum_{i|j=x}f(i)\times g(j)\\ h(x)=\sum_{i\&j=x}f(i)\times g(j)\\ h(x)=\sum_{i⊕j=x}f(i)\times g(j)\\ \end{aligned} \right.\]

    |表示二进制或运算&表示二进制与运算表示二进制异或运算

    怎么做呢??——还是按着\(FFT\)的思路来
    考虑能不能将多项式转化一下,变成位对位相乘,最后转化回去就是所求多项式
    这就是\(FWT\)完成的使命了!!
    前人已经找到了规律,备矣,我只是记录规律的正确性
    因为模仿FFT,所以n应该也为2的幂,下文意义默认为2的幂


    or(或)卷积

    原理

    求:

    \[h(x)=\sum_{i|j=x}f(i)\times g(j) \]

    定义多项式\(A\)转化规律$$A'[i]=\sum_{i&j=i} A[j]$$


    \[h'[y]=\sum_{x\& y=x}h[x] \]

    \[=\sum_{x\& y=x}\sum_{i|j=x}f[i]\times g[j] \]

    i|j=x表明i,j都是x的子集,而x又是y子集\(\Rightarrow\)i,jy的子集

    \[=\sum_{i\&y=i}\sum_{j\&y=j}f[i]\times g[j]$$$$=(\sum_{i\&y=i}f[i] )\times(\sum_{j\&y=j}g[j])$$$$=f'[y]\times g'[y] \]

    成功实现位对位相乘,yeah!!举国同庆
    在这里插入图片描述


    最近重温\(FFT,NTT\)竟然变成了懂原理,看代码看半天??
    所以接下来说一下实现,思路是递归,写法是递推,都好懂

    FWT_or正变换

    考虑模仿\(FFT\),也是不断\(/2\),将时间复杂度降下去
    \(y<\frac{n}{2}\),则有

    \[h'[y]=\sum_{x\& y=x}f[x]\times g[x] \]

    \[h'[y+\frac{n}{2}]=\sum_{x\&(y+\frac{n}{2})=x}f[x]\times g[x] \]

    \(h'[y+\frac{n}{2}]\)的子集\(x\)拆分成两类分别考虑
    ①:\(x<\frac{n}{2}\),即二进制最高位是\(0\),则有\(x\& y=x\&(y+\frac{n}{2})\)

    \[\sum_{x\&y=x}f[x]\times g[x]=h'[y] \]

    ②:\(x>\frac{n}{2}\)

    \[\sum_{x\&(y+\frac{n}{2})}f[x]\times g[x] \]

    将情况②当成新的多项式继续往下\(FWT\),长度砍掉一半,循环上面的操作
    最后要将左半部分的答案加到右半部分

    在这里插入图片描述
    不断往下,然后把左边蓝色计算出来,再加给右边红色,从下往上递推
    在这里插入图片描述

    FWT_or逆变换

    就是从上往下,先减掉再往下

    模板

    \(opt=1\)是正变换

    void FWT_or( int *v, int f ) {
    	for( int i = 1;i < n;i <<= 1 )
    		for( int j = 0;j < n;j += ( i << 1 ) )
    			for( int k = 0;k < i;k ++ )
    				v[i + j + k] = ( v[i + j + k] + f * v[j + k] ) % mod;
    }
    

    and(与)卷积

    原理

    问:

    \[h(x)=\sum_{i\&j=x}f(i)\times g(j) \]

    定义多项式变换规律

    \[A'[i]=\sum_{i\&j=i}A[j] \]


    \[h'[y]=\sum_{x\&y=y}h[x] \]

    \[=\sum_{x\&y=y}\sum_{i\&j=x}f[i]\times g[j] \]

    i&j=x说明xi,j的子集,而y又是x的子集\(\Rightarrow\)yi,j的子集

    \[=\sum_{i\&y=y}\sum_{j\&y=y}f[i]\times g[j] \]

    \[=(\sum_{i\&y=y}f[i])\times (\sum_{j\&y=y}g[j]) \]

    \[=f'[y]\times g'[y] \]

    FWT_and正变换

    与FWT_or差不多的思路
    \(y\ge \frac{n}{2}\)

    \[h'[y]=\sum_{x\&y=y}f[x]\times g[x] \]

    \[h'[y-\frac{n}{2}]=\sum_{x\&(y-\frac{n}{2})=(y-\frac{n}{2})}f[x]\times g[x] \]

    同样的将\(h'[y-\frac{n}{2}]\)符合条件的\(x\)分成两类来考虑
    ①:\(x\ge \frac{n}{2}\)
    说明\(x\)最高位是\(1\)
    不考虑\(x\)的最高位后,\(y-\frac{n}{2}\)仍是其子集
    那么\(y\)也是\(x\)的子集

    \[\sum_{x\&y=y}f[x]\times g[x]=h'[y] \]

    ②:\(x<\frac{n}{2}\)

    \[\sum_{x\&(y-\frac{n}{2})=(y-\frac{n}{2})}f[x]\times g[x] \]

    将此情况当成新的多项式继续往下求解
    在这里插入图片描述


    在这里插入图片描述
    从下往上算完后将右半部分红色加到左半部分蓝色里,再往上回溯

    FWT_and逆变换

    从上往下,先左减去右,再下放

    模板

    void FWT_and( int *v, int f ) {
    	for( int i = 1;i < n;i <<= 1 )
    		for( int j = 0;j < n;j += ( i << 1 ) )
    			for( int k = 0;k < i;k ++ )
    				v[j + k] = ( v[j + k] + v[j + k + i] * f ) % mod;
    }
    

    xor(异或)卷积

    在这里插入图片描述

    原理

    问:

    \[h(x)=\sum_{i⊕j=x}f(i)\times g(j) \]

    定义多项式变换规律

    \[A'[i]=\sum_{cnt(i\&j_1)\%2=0}A[j_1]-\sum_{cnt(i\&j_2)\%2=1}A[j_2] \]

    \(cnt(i)\)表示\(i\)二进制有多少个\(1\)
    在这里插入图片描述

    \[h'[y]=\sum_{cnt(y\&x_1)\%2=0}h[x_1]-\sum_{cnt(y\&x_2)\%2=1}h[x_2] \]

    \[=\sum_{cnt(y\&x_1)\%2=0}\sum_{i_1⊕j_1=x_1}f[i_1]\times g[j_1]-\sum_{cnt(y\&x_2)\%2=1}\sum_{i_2⊕j_2=x_2}f[i_2]\times g[j_2] \]

    在这里插入图片描述似乎卡住了,推不下去了??这可不行,证明不了正确性啊!
    那没办法了,正着推不下去,老子倒着往回退可还行
    仗着前人的正确性疯狂整

    \[f'[x]\times g'[x]$$$$=(\sum f[i]-\sum f[j])\times (\sum g[i]-\sum g[j]) \]

    \[cnt(x\& i)\%2=0,cnt(x\&j)\%2=1 \]

    \[=\sum f[i]g[i]+\sum f[j]g[j]-\sum f[i]g[j]-\sum f[j]g[i] \]

    这里需要抽象理解式子,才能建立关联,发现与\(h'[x]\)展开是一致的


    不好意思我太菜了,建立不了关联——那就换一种证法
    在这里插入图片描述

    \[x\&(i⊕j)\equiv x\& i+x\&j\ \ \ \ (mod\ \ \ 2) \]

    我们采取暴力分类讨论智障求证,下面i,j,x均表示其二进制某一位上为\(1/0\)

    1. i=1,j=1,x=0
      \(i⊕j=0,(i⊕j)\&x=0\)
      \(x\&i=0,x\&j=0,x\&i+x\&j=0\)
    2. i=0,j=0,x=0
      \(i⊕j=0,(i⊕j)\&x=0\)
      \(x\&i=0,x\&j=0,x\&i+x\&j=0\)
    3. i=1,j=0,x=0
      \(i⊕j=1,(i⊕j)\&x=0\)
      \(x\&i=0,x\&j=0,x\&i+x\&j=0\)
    4. i=0,j=1,x=0
      \(i⊕j=1,(i⊕j)\&x=0\)
      \(x\&i=0,x\&j=0,x\&i+x\&j=0\)
    5. i=0,j=0,x=1
      \(i⊕j=0,(i⊕j)\&x=0\)
      \(x\&i=0,x\&j=0,x\&i+x\&j=0\)
    6. i=0,j=1,x=1
      \(i⊕j=1,(i⊕j)\&x=1\)
      \(x\&i=0,x\&j=1,x\&i+x\&j=1\)
    7. i=1,j=0,x=1
      \(i⊕j=1,(i⊕j)\&x=1\)
      \(x\&i=1x\&j=0,x\&i+x\&j=1\)
    8. i=1,j=1,x=1
      \(i⊕j=0,(i⊕j)\&x=0\)
      \(x\&i=1,x\&j=1,x\&i+x\&j=2\)
      此情况注意是在取模2下,所以奇偶还是没变

    \(A'\)换一种表达方式

    \[A'[i]=\sum (-1)^{cnt(i\&j)\% 2}A[j] \]

    重新推一遍式子
    科研就是不断失败重头再来!!

    \[h'[y]=\sum(-1)^{cnt(y\&x)\%2}h[x]=\sum(-1)^{cnt(y\&x)\%2}\sum_{i⊕j=x}f[i]g[j] \]

    \[=\sum (-1)^{cnt(y\&(i⊕j))\%2}f[i]g[j]=\sum (-1)^{cnt(y\&i)+cnt(y\&j)}f[i]g[j] \]

    \[=\sum (-1)^{cnt(y\&i)}f[i]\times \sum (-1)^{cnt(y\&j)}g[j] \]

    \[=f'[y]\times g'[y] \]

    在这里插入图片描述


    FWT_xor正变换

    有了前面两种简单的铺垫,这里的思路是一致的,不再写式子

    \(y<\frac{n}{2}\)
    将符合条件的\(x\)同样分成两类
    ①:\(x<\frac{n}{2}\)
    ②:\(x\ge \frac{n}{2}\)
    \(x\&y\)\(1\)个数的奇偶性并没有发生改变
    直接加起来

    \(y\ge \frac{n}{2}\)
    将符合条件的\(x\)同样分成两类
    ①:\(x<\frac{n}{2}\)
    ②:\(x\ge \frac{n}{2}\)
    情况②的\(x\&y\)最高位为\(1\),个数奇偶性发生改变,前面带的符号变成负号

    \[FWT(A)=(FWT(B)+FWT(C),FWT(B)-FWT(C)) \]

    FWT_xor逆变换

    就相当于解一个二元一次方程,已知和差

    \[IFWT(A)=(\frac{IFWT(B)+IFWT(C)}{2},\frac{IFWT(B)-IFWT(C)}{2}) \]

    模板

    void FWT_xor( int *v, int f ) {
    	for( int i = 1;i < n;i <<= 1 )
    		for( int j = 0;j < n;j += ( i << 1 ) ) 
    			for( int k = 0;k < i;k ++ ) {
    				int x = v[j + k], y = v[j + k + i];
    				v[j + k] = ( x + y ) % mod;
    				v[j + k + i] = ( x - y + mod ) % mod;
    				if( f == -1 ) {
    					v[j + k] = v[j + k] * inv % mod;
    					v[j + k + i] = v[j + k + i] * inv % mod;
    				}
    			}
    }
    

    例题

    听得懂原理:理解记忆模板
    听不懂:强化训练
    在这里插入图片描述

    T1:Card Game

    title
    Alice and Bob now love to play a card game. Everyone is starting n cards, each card has no more than m attribute. Now they need finish Q tasks, each task will require everyone to give a card, and then make up the attribute types that the task demands (e.g. the task required attributes A, B, C, Alice’s card contains A B and Bob’s card contains B, C. they can use this union to finish the task).
    For each task, How many ways that Alice and Bob can do this task.
    Input
    here are T cases. (T<=20) (about 5 test cases n>=1000 m>=12 Q>=1000)
    For each test case.The first line contains two integers n,m(n<=100000, m<=18), which indicates the number of cards which each one has and total attributes.
    The next line contain n binary numbers indicates the cards Alice has. The ith binary number m_i indicates the attributes each card have. (m_i <2^18)
    (if m_i = 10011 , this card has the first, second and fifth attrtbutes)
    he next line contain n binary numbers indicates the cards Bob has.
    Then contain one integer Q which is the number of Tasks.
    Then next Q lines, each contain one binary number q_i which indicates the attributes they need to make. (qi < 2^18)
    Output
    For each test case, you first output one line “Case #%d:”
    Then output q lines, each line contains one which indicates the ways they can finish this task.
    Sample Input

    1
    4 4
    1001 11 1100 1000
    1110 1001 10 100
    2
    1100
    111
    

    Sample Output

    Case #1:
    2
    1
    

    solution
    板题,就是套\(FWT\_or\)
    code

    #include <cstdio>
    #include <cstring>
    #define mod 998244353
    #define MAXN 200000
    int n, m, N, Q;
    long long A[MAXN], B[MAXN], v[MAXN], c[MAXN];
    long long inv = ( mod + 1 ) >> 1;
    char s[30];
    
    long long read() {
    	scanf( "%s", s );
    	int len = strlen( s );
    	long long x = 0;
    	for( int i = 0;i < len;i ++ )
    		x = ( x << 1 ) + ( s[i] - '0' );
    	return x;
    }
    
    void FWT_or( long long *v, int opt ) {
    	for( int i = 2;i <= N;i <<= 1 )
    		for( int p = i >> 1, j = 0;j < N;j += i )
    			for( int k = j;k < j + p;++ k )
    				v[k + p] += v[k] * opt;
    }
    
    int main() {
    	int T, n, m;
    	scanf( "%d", &T );
    	for( int t = 1;t <= T;t ++ ) {
    		printf( "Case #%d:\n", t );
    		scanf( "%d %d", &n, &m );
    		N = 1 << m;
    		memset( A, 0, sizeof( A ) );
    		memset( B, 0, sizeof( B ) );
    		for( int i = 1;i <= n;i ++ ) A[read()] ++;
    		for( int i = 1;i <= n;i ++ ) B[read()] ++;
    		FWT_or( A, 1 ), FWT_or( B, 1 );
    		for( int i = 0;i < N;i ++ )
    			A[i] = A[i] * B[i];
    		FWT_or( A, -1 );
    		scanf( "%d", &Q );
    		while( Q -- ) printf( "%lld\n", A[read()] );
    	}
    	return 0;
    }
    

    T2:Tree Cutting

    title
    Byteasar has a tree \(T\) with \(n\) vertices conveniently labeled with \(1,2,...,n\). Each vertex of the tree has an integer value \(v_i\).
    The value of a non-empty tree \(T\) is equal to \(v_1\oplus v_2\oplus ...\oplus v_n\), where \(\oplus\) denotes bitwise-xor.
    Now for every integer \(k\) from \([0,m)\), please calculate the number of non-empty subtree of \(T\) which value are equal to \(k\).
    A subtree of \(T\) is a subgraph of \(T\) that is also a tree.
    Input
    The first line of the input contains an integer \(T(1\leq T\leq10)\), denoting the number of test cases.
    In each test case, the first line of the input contains two integers \(n(n\leq 1000)\) and \(m(1\leq m\leq 2^{10})\), denoting the size of the tree \(T\) and the upper-bound of \(v\).
    The second line of the input contains \(n\) integers \(v_1,v_2,v_3,...,v_n(0\leq v_i < m)\), denoting the value of each node.
    Each of the following \(n-1\) lines contains two integers \(a_i,b_i\), denoting an edge between vertices \(a_i\) and \(b_i(1\leq a_i,b_i\leq n)\).
    It is guaranteed that \(m\) can be represent as \(2^k\), where \(k\) is a non-negative integer.
    Output
    For each test case, print a line with \(m\) integers, the \(i\)-th number denotes the number of non-empty subtree of \(T\) which value are equal to \(i\).
    The answer is huge, so please module \(10^9+7\).
    Sample Input

    2
    4 4
    2 0 1 3
    1 2
    1 3
    1 4
    4 4
    0 1 3 1
    1 2
    1 3
    1 4
    

    Sample Output

    3 3 2 3
    2 4 2 3
    

    solution
    \(dp[u][i]\):表示以\(u\)为根的子树内,异或值为\(j\)的方案数
    有一个很暴力的转移

    \[dp[u][i]=\sum_{j\otimes k=i}dp[u][j]*dp[v][k],v∈son[u] \]

    发现直接可以\(FWT\_xor\),直接\(dfs\)暴力做就降了一个\(m\)
    code

    #include <cstdio>
    #include <vector>
    #include <cstring>
    using namespace std;
    #define mod 1000000007
    #define ll long long
    #define MAXN 1050
    vector < int > G[MAXN]; 
    const ll inv = ( mod + 1 ) >> 1;
    int T, n, m, N;
    ll ans[MAXN];
    ll dp[MAXN][MAXN];
    
    void FWT( ll *w, int opt ) {
    	for( int i = 2;i <= N;i <<= 1 )
    		for( int p = i >> 1, j = 0;j < N;j += i )
    			for( int k = j;k < j + p;k ++ ) {
    				ll x = w[k], y = w[k + p];
    				w[k] = ( x + y ) % mod;
    				w[k + p] = ( x - y + mod ) % mod;
    				if( opt == -1 )
    					w[k] = w[k] * inv % mod, w[k + p] = w[k + p] * inv % mod;
    			}
    }
    
    void dfs( int u, int fa ) {
    	FWT( dp[u], 1 );
    	for( int i = 0;i < G[u].size();i ++) {
    		int v = G[u][i];
    		if( v == fa ) continue;
    		dfs( v, u );
    		for( int j = 0;j < N;j ++ )
    			dp[u][j] = dp[u][j] * dp[v][j] % mod;
    	}
    	FWT( dp[u], -1 );
    	dp[u][0] = ( dp[u][0] + 1 ) % mod;//一个子树是可以不选的 所以要+1
    	FWT( dp[u], 1 ); 
    }
    
    int main() {
    	scanf( "%d", &T );
    	while( T -- ) {
    		scanf( "%d %d", &n, &m );
    		memset( dp, 0, sizeof( dp ) );
    		memset( ans, 0, sizeof( ans ) );
    		for( int i = 1;i <= n;i ++ ) G[i].clear();
    		N = m;
    		for( int i = 1, x;i <= n;i ++ )
    			scanf( "%d", &x ), dp[i][x] ++;
    		for( int i = 1, u, v;i < n;i ++ ) {
    			scanf( "%d %d", &u, &v );
    			G[u].push_back( v ), G[v].push_back( u );
    		}
    		dfs( 1, 0 );
    		for( int i = 1;i <= n;i ++ )
    			FWT( dp[i], -1 );
    		for( int i = 1;i <= n;i ++ )
    			dp[i][0] = ( dp[i][0] - 1 + mod ) % mod;
    		for( int i = 1;i <= n;i ++ )
    			for( int j = 0;j < m;j ++ )
    				ans[j] = ( ans[j] + dp[i][j] ) % mod;
    		printf( "%lld", ans[0] );
    		for( int i = 1;i < m;i ++ )
    			printf( " %lld", ans[i] ); 
    		printf( "\n" );
    	}	
    	return 0;
    }
    

    在这里插入图片描述

  • 相关阅读:
    hdoj 4006 The kth great number【优先队列】
    hdoj 1509 Windows Message Queue【优先队列】
    nyoj 55 懒省事的小明【优先队列】
    hdoj 1896 Stones【优先队列】
    nyoj 757 期末考试【优先队列+贪心】
    hdoj 2147 kiki's game【博弈】
    hdoj 1873 看病要排队【优先队列】
    hdoj 1789 Doing Homework again
    nyoj 1036 非洲小孩【贪心区间选点】
    转:栈和队列小知识【STL用法】
  • 原文地址:https://www.cnblogs.com/mamamoo/p/13093885.html
Copyright © 2011-2022 走看看