$FWT$是用来处理位运算(异或、与、或)卷积的一种变换。位运算卷积是什么?形如$f[i]=\sum\limits_{j\oplus k==i}^{ }g[j]*h[k]$的卷积形式(其中$\oplus$为位运算)就是位运算卷积。若是暴力枚举的话,时间复杂度是$O(n^2)$,但运用$FWT$来解决就可达到$O(nlog_{n})$的时间复杂度。$FST$则是借助$FWT$来进行的对子集卷积的优化,至关于$FWT$的一个应用。函数
对于与运算,有一个结论:$(i\&j)\&k==k<-->(i\&k==k)\&\&(j\&k==k)$。优化
那么咱们就能够构造一个求父集和的函数$F(i)=\sum\limits_{j\&i==i}^{ }f(i)$,由此咱们能够推出:spa
$G(k)*H(k)$blog
$=\sum\limits_{i\&k==k}^{ }g(i)\sum\limits_{j\&k==k}^{ }h(j)$it
$=\sum\limits_{(i\&j)\&k==k}^{ }g(i)*h(j)$class
$=\sum\limits_{t\&k==k}^{ }\sum\limits_{i\&j==t}^{ }g(i)*h(j)$扩展
由于$f(t)=\sum\limits_{i\&j==t}^{ }g(i)*h(j)$,二进制
全部上式$=\sum\limits_{t\&k==k}^{ }f(t)=F(k)$im
所以咱们只须要将$g,h$正变换成$G,H$,而后对应位相乘获得$F$后再将$F$逆变换回去便可获得$f$。集合
那么如何正变换?
如下面为例:
00 a
01 b
10 c
11 d
咱们从最低位开始考虑,每次只考虑只有当前位不一样的两个数之间的影响。显然求父集只有$1$会对$0$有贡献,所以咱们将$01$的值加到$00$上,将$11$的值加到$10$上,再看下一位,一样将$11$的值加到$01$上,将$10$的值加到$00$上。这样最后$00$的值为$a+b+c+d$,$01$的值为$b+d$,$10$的值为$c+d$,$11$的值为$d$。一样逆变换就是将$1$的值从$0$上减掉便可。
附上代码
void fwt_and(int *a,int opt) { for(int k=2;k<=n;k<<=1) { for(int i=0,t=k>>1;i<n;i+=k) { for(int j=i;j<i+t;j++) { if(opt==1) { a[j]=(a[j]+a[j+t])%mod; } else { a[j]=(a[j]-a[j+t]+mod)%mod; } } } } }
或卷积和与卷积相似,对于或卷积一样有结论:$(i|j)|k==k<-->(i|k==k)\&\&(j|k==k)$
此次咱们须要构造一个求子集和的函数$G(i)=\sum\limits_{j|i==i}^{ }g(j)$,推导过程和与卷积相似。
对于正变换,显然只有$0$对$1$有贡献;对于逆变换,只须要将$0$的值从$1$中减掉便可。
附上代码
void fwt_or(int *a,int opt) { for(int k=2;k<=n;k<<=1) { for(int i=0,t=k>>1;i<n;i+=k) { for(int j=i;j<i+t;j++) { if(opt==1) { a[j+t]=(a[j+t]+a[j])%mod; } else { a[j+t]=(a[j+t]-a[j]+mod)%mod; } } } } }
对于异或卷积,咱们设$bit(i)$表明$i$的二进制中$1$的奇偶性,所以有一个结论(这里异或用$\oplus$表示):$bit(i\&k)\oplus bit(j\&k)=bit((i\oplus j)\&k)$
对于原多项式$g$构造$G(i)=\sum\limits_{j=0}^{2^n-1}(-1)^{bit(j\&i)}g(j)$
开始推导:
$G(k)*H(k)$
$=\sum (-1)^{bit(i\&k)}g(i)\sum (-1)^{bit(j\&k)}h(j)$
$=\sum (-1)^{bit((i\oplus j)\& k)}g(i)*h(j)$
$=\sum (-1)^{bit(t\&k)}\sum\limits_{i\oplus j==t}^{ }g(i)*h(j)$
$=\sum (-1)^{bit(t\&k)}f(t)$
$=F(k)$
对于正变换,咱们一样从最低位向最高位考虑,每次只考虑只有当前位不一样的两个数之间的影响。对于每对$0$和$1$(设值分别为$a$和$b$),$0\&0=0$和$0\&1=0$都不会影响$bit$的值,因此$0$那个位置的值变成$a+b$;$1\&0=0$不会影响$bit$的值,但$1\&1=1$会影响$bit$的值(至关于在前面乘上一个$-1$的系数),所以$1$那个位置的值变成$a-b$。对于逆变换,至关于咱们如今知道两个位置$x=a+b$,$y=a-b$,求$a$和$b$,能够获得$a=\frac{x+y}{2},b=\frac{x-y}{2}$。
附上代码
void fwt_xor(int *a,int opt) { int tmp; for(int k=2;k<=n;k<<=1) { for(int i=0,t=k>>1;i<n;i+=k) { for(int j=i;j<i+t;j++) { tmp=a[j]; a[j]=(a[j]+a[j+t])%mod; a[j+t]=(tmp-a[j+t]+mod)%mod; if(opt==-1) { a[j]=1ll*a[j]*inv%mod; a[j+t]=1ll*a[j+t]*inv%mod; } } } } }
能够发现二进制的异或运算至关于不进位加法即每一位对应相加后对$2$取模,而与运算至关于不进位乘法即每一位对应相乘后对$2$取模,$bit$至关于求二进制每一位的和对$2$取模。那么咱们将这些在二进制下的运算扩展到$K$进制能够发现一样知足上述的结论,但对于从$g$求$G$的部分每一个数前面的系数的底数是$-1$,显然这个系数不能扩展到$K$进制。那么咱们如今就须要找一个系数$w$知足$w^0,w^1,w^2……w^{k-1}$都各不相同且$w^i=w^{i\%k}$。从$FFT$中咱们知道了复数单位根这个东西,那么咱们彻底能够将$w$取$K$次单位根,这样就能够知足以上性质了!类比二进制亦或的正变换也能够得出$K$进制的正变换。
$FST$一般用来优化一类子集$DP$,例如$f(S)=\sum g(T)*h(S-T)$,其中$T$是$S$的子集。
对于这个方程咱们不能直接用$FWT$卷积,由于若是$g(i)*h(j)$会对$f(k)$有贡献就要求$i|j=k,i\&j=0$。
显然位运算卷积不能同时知足这两个要求,那么咱们将方程变成二维表示$f[S][i]=\sum\limits_{j=1}^{i}\sum\limits_{a|b==S}^{ }g[a][j]*h[b][i-j]$
其中的第二维表示集合大小,对于$f[S][i]$只有当$i=|S|$时才有值,这样第一维保证并集为$S$,第二维保证交集为空集,因此其余不合法的状态不会影响答案。
这样咱们就能对式子进行卷积:$F[S][i]=\sum\limits_{j=1}^{i}G[S][j]*H[S][i-j]$。求出每一个$F$的值再$FWT$逆变换回去便可。
原题中的$f[S]$就是二维状态中的$f[S][|S|]$。
时间复杂度为$O(2^n*n^2)$。