标签:任务 研究 ace ons play 运算 case putc bit
\(fwt\)是一种快速计算位运算卷积的算法,一般包括按位或卷积,按位与卷积和异或卷积。
对于多项式\(A,B,C\),定义\(\oplus\)为卷积符号,即\(A\oplus B = C\)。
那么,按位或卷积就是:
\[
C_k=\sum_{i~or~j=k}A_i\cdot B_j
\]
类比于\(FFT\),现在,我们的任务就是找到一种变换,记这种变换为\(fwt(A)\),则要满足\(fwt(A)\times fwt(B)=fwt(C)\),其中\(\times\)表示每一位相乘,且\(A\oplus B=C\)。
经过前人的大力研究,可以发现:
\[
fwt(A)_i=\sum_{j~or~i=i}A_j
\]
是满足性质的,证明很简单,直接带进去可得:
\[
\begin{align}
fwt(C)_k&=\sum_{j~or~k=k}\sum_{a~or~b=k}A_a\cdot B_b\&=\sum_{a~or~k=k}A_a\cdot \sum_{b~or~k=k}B_b\&=fwt(A)_k\cdot fwt(B)_k
\end{align}
\]
即得证。
那么,考虑怎样快速的进行\(fwt\)变换。
然后有一个这样的式子:
\[
fwt(A)=
\begin{cases}
(fwt(A_1),fwt(A_1)+fwt(A_2))&n>0\A_0&n=0
\end{cases}
\]
其中,\((A,B)\)表示把两个多项式的系数拼起来,感性理解一下就好了。
\(A_1\)表示多项式前半段,\(A_2\)表示后半段。
当\(n=0\)的时候显然,我们只需要关心上面那个是为什么就好了。
对于前半段的第\(i\)项,\(i\)的最高位肯定是\(0\),那么后半段显然对他没有影响,前半段的影响就是\(fwt(A_1)_i\)。
对于后半段的第\(i\)项,\(i\)的最高位是\(1\),所以最高位取\(0\)时是\(fwt(A_1)_i\),取\(1\)时是\(fwt(A_2)_i\),所以一共就是\(fwt(A_1)+fwt(A_2)\)。
然后这玩意形式其实和\(FFT\)差不太多,复杂度也是\(O(n\log n)\)。
代码:
void fwt_or(int *r) {
for(int i=1;i<n;i<<=1)
for(int j=0;j<n;j+=(i<<1))
for(int k=0;k<i;k++)
r[i+j+k]=(r[i+j+k]+r[j+k])%mod;
}
和上面差不多的,定义:
\[
fwt(A)_i=\sum_{j\&i=i}A_i
\]
证明也差不多,这里不赘述了。
那么,算的话就是:
\[
fwt(A)=
\begin{cases}
(fwt(A_1)+fwt(A_2),fwt(A_2))&n>0\A_0&n=0
\end{cases}
\]
只要考虑按位与的性质,高位为\(1\)时只能选高位为\(1\)的,否则都能选。
代码:
void fwt_and(int *r) {
for(int i=1;i<n;i<<=1)
for(int j=0;j<n;j+=(i<<1))
for(int k=0;k<i;k++)
r[j+k]=(r[i+j+k]+r[j+k])%mod;
}
这里的定义就不是很相同了。
定义:
\[
fwt(A)_i=\sum_{j=0}^{n}(-1)^{cnt(i\&j)}A_j
\]
其中,\(i\&j\)表示按位与,\(cnt(x)\)表示\(x\)二进制下\(1\)的个数。(这到底是怎么想到的。。)
带进去交换下枚举顺序可得:
\[
\begin{align}
fwt(C)_i&=\sum_{j=0}^{n}(-1)^{cnt(i\&j)}C_j\&=\sum_{j=0}^{n}(-1)^{cnt(i\&j)}\sum_{a\oplus b=j}A_aB_b\&=\sum_{a=0}^{n}A_a\sum_{b=0}^{n}B_b(-1)^{cnt(i\&(a\oplus b))}
\end{align}
\]
我们考虑下指数上的那一块东西:\(cnt(i\&(a\oplus b))\),分情况讨论下这个与\(cnt(i\&a)+cnt(i\&b)\)的关系:(由于多位和一位没有区别,这里只讨论一位)
若\(i\)为\(0\),显然这一位不计入答案,不管。
若\(a,b\)都为\(1\)的话,\(a\oplus b=0\),不计入答案,但是注意到这里是\((-1)\)的指数,其实\((-1)^0=(-1)^2\),不妨看做是\(2\),那么这两个相等。
若\(a,b\)有一个为\(1\),前后显然相等,都为\(1\)。
若\(a,b\)都为\(0\),显然也相等,都为\(0\)。
所以式子可以改写成这样:
\[
\begin{align}
fwt(C)_i&=\sum_{a=0}^{n}A_a\sum_{b=0}^{n}B_b(-1)^{cnt(i\&a)+cnt(i\&b)}\&=\sum_{a=0}^{n}(-1)^{cnt(i\&a)}A_a\sum_{b=0}^{n}(-1)^{cnt(i\&b)}B_b\&=fwt(A)_i\cdot fwt(B)_i
\end{align}
\]
所以,证毕。
那么,快速做这个的式子:
\[
fwt(A)=
\begin{cases}
(fwt(A_1)+fwt(A_2),fwt(A_1)-fwt(A_2))&n>0\A_0&n=0
\end{cases}
\]
具体的,考虑前一半的时候,最高位为\(0\),直接加起来就好了。
对于后一半,最高位为\(1\),如果选的数最高位也为\(1\),\(cnt\)就多了\(1\),也就是整体多乘了个\(-1\),所以就是\(fwt(A_1)-fwt(A_2)\)。
代码:
void fwt_xor(int *r) {
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=r[j+k],y=r[i+j+k];
r[j+k]=(x+y)%mod,r[i+j+k]=(x-y)%mod;
}
}
知道了上面的,这玩意其实就很简单了。
对于按位或,就是知道了\(fwt(A_1)\)和\(fwt(A_1)+fwt(A_2)\),求出两个分别是多少,直接减一下就完了:
\[
ifwt(A)=(ifwt(A_1),ifwt(A_2)-ifwt(A_1))
\]
对于按位与,也差不多:
\[
ifwt(A)=(ifwt(A_1)-ifwt(A_2),ifwt(A_2))
\]
对于异或,是知道\(fwt(A_1)+fwt(A_2)\)和\(fwt(A_1)-fwt(A_2)\),那么加起来除以\(2\)就是第一个,减一下除以\(2\)就是第二个,即:
\[
ifwt(A)=(\frac{ifwt(A_1)+ifwt(A_2)}{2},\frac{ifwt(A_1)-ifwt(A_2)}{2})
\]
给一个模板大全吧,题目来自luogu4717。
#include<bits/stdc++.h>
using namespace std;
void read(int &x) {
x=0;int f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-f;
for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';x*=f;
}
void print(int x) {
if(x<0) putchar('-'),x=-x;
if(!x) return ;print(x/10),putchar(x%10+48);
}
void write(int x) {if(!x) putchar('0');else print(x);putchar('\n');}
const int maxn = 2e5+10;
const int mod = 998244353;
const int inv2 = 499122177;
int bit,n,a[maxn],b[maxn],c[maxn],ina[maxn],inb[maxn];
void fwt_or(int *r,int op) {
for(int i=1;i<n;i<<=1)
for(int j=0;j<n;j+=(i<<1))
for(int k=0;k<i;k++)
if(op==1) r[i+j+k]=(r[i+j+k]+r[j+k])%mod;
else r[i+j+k]=(r[i+j+k]-r[j+k])%mod;
}
void fwt_and(int *r,int op) {
for(int i=1;i<n;i<<=1)
for(int j=0;j<n;j+=(i<<1))
for(int k=0;k<i;k++)
if(op==1) r[j+k]=(r[i+j+k]+r[j+k])%mod;
else r[j+k]=(r[j+k]-r[i+j+k])%mod;
}
void fwt_xor(int *r,int op) {
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=r[j+k],y=r[i+j+k];
if(op==1) r[j+k]=(x+y)%mod,r[i+j+k]=(x-y)%mod;
else r[j+k]=1ll*(x+y)*inv2%mod,r[i+j+k]=1ll*(x-y)*inv2%mod;
}
}
int main() {
read(bit);n=1<<bit;
for(int i=0;i<n;i++) read(ina[i]);
for(int i=0;i<n;i++) read(inb[i]);
// or
memcpy(a,ina,sizeof ina);memcpy(b,inb,sizeof inb);
fwt_or(a,1),fwt_or(b,1);for(int i=0;i<n;i++) a[i]=1ll*a[i]*b[i]%mod;
fwt_or(a,-1);for(int i=0;i<n;i++) printf("%d ",(a[i]+mod)%mod);puts("");
// and
memcpy(a,ina,sizeof ina);memcpy(b,inb,sizeof inb);
fwt_and(a,1),fwt_and(b,1);for(int i=0;i<n;i++) a[i]=1ll*a[i]*b[i]%mod;
fwt_and(a,-1);for(int i=0;i<n;i++) printf("%d ",(a[i]+mod)%mod);puts("");
// xor
memcpy(a,ina,sizeof ina);memcpy(b,inb,sizeof inb);
fwt_xor(a,1),fwt_xor(b,1);for(int i=0;i<n;i++) a[i]=1ll*a[i]*b[i]%mod;
fwt_xor(a,-1);for(int i=0;i<n;i++) printf("%d ",(a[i]+mod)%mod);puts("");
return 0;
}
标签:任务 研究 ace ons play 运算 case putc bit
原文地址:https://www.cnblogs.com/hbyer/p/10308815.html