FWT能干什么:
FWT可以对于两个数组a和b,求出他们的位运算卷积c,使得c[k]= ∑a[i]∗b[j] ∑ a [ i ] ∗ b [ j ] (对于所有的i和j 满足 i位运算j等于k )
我们先讲与卷积和或卷积,最后再讲异或卷积
一个简单的问题:
给定数组a,求数组b,使得b[i]= ∑a[j] ∑ a [ j ] (对于所有的j满足j|i==i)
我们考虑按位分治,先把数组长度用0补到2的幂
先不考虑最高位,那么我们可以把长度为2^l的原数组按最高位为0和最高位为1分为两部分
递归处理这两部分,假设我们现在已经得到了对于所有最高位为0的下标求的b数组b0和对所有最高位为1的下标求的b数组b1,他们的长度均为2^(l-1)
那么我们现在考虑最高位的影响。我们把计入最高位影响之后的长度为2^l的b数组分为左右两部分,记为bn0和bn1,即最高位为0的部分和最高位为1的部分,他们的长度也都为2^(l-1)
那么对于任意的i和j,如果j|i不等于i,给i和j添加一个最高位之后的j|i一定也不等于i,所以bn0[i]和bn1[i]只与b0[i]和b1[i]有关
而如果j|i等于i,那么假设给i添加最高位ih,给j添加最高位jh,添加最高位后j|i是否等于i就只与jh|ih是否等于ih有关
那么我们显然就可以得到bn0[i]=b0[i],bn1[i]=b0[i]+b1[i]
觉得不显然可以YY一下
如果是要求使得b[i]= ∑a[j] ∑ a [ j ] (对于所有的j满足j&i==i)的话,那么就是bn0[0]=b0[i]+b1[i],bn1[i]=b1[i]
这样的话我们就可以在n log n的时间内求出b数组
我们会发现这个做法事实上就是或卷积的正变换
在后面我们也将求b数组叫做正变换
或卷积、与卷积的原理:
或卷积和与卷积事实上基于如下原理:
首先: k|k=k k | k = k ,k&k=k k&k=k
则有:
i&k=k并且j&k=k等价于(i&j)&k=k (i&j)&k=k 原理一
i|k=k并且j|k=k等价于 (i|j)|k=k ( i | j ) | k = k 原理二
那么如果要求数组a和数组b的与卷积或者或卷积,我们就只需要求出a的正变换a’,即 a′[i]=∑b[j] a ′ [ i ] = ∑ b [ j ] ,(j&i==i) (j&i==i) 和b的正变换b’,即 b′[i]=∑a[j] b ′ [ i ] = ∑ a [ j ] ,(j&i==i) (j&i==i) ,然后另 c′[i]=a′[i]∗b′[i] c ′ [ i ] = a ′ [ i ] ∗ b ′ [ i ] ,由原理一可得,那么容易发现c’就是a和b的卷积做正变换的结果
那么我们现在就只需要考虑给你一个正变换之后的结果,如何做逆变换求出原数组
逆变换:
逆变换的过程事实上就是一个解密的过程,而正变换相当于加密
我们还是按位考虑,在正变换的时候,我们令 bn0[i]=b0[i],bn1[i]=b0[i]+b1[i], b n 0 [ i ] = b 0 [ i ] , b n 1 [ i ] = b 0 [ i ] + b 1 [ i ] , 那么如果你现在知道的是bn0和bn1,那么容易知道 b0[i]=bn0[i],b1[i]=bn1[i]−bn0[i] b 0 [ i ] = b n 0 [ i ] , b 1 [ i ] = b n 1 [ i ] − b n 0 [ i ]
与运算的逆变换同理
然后再递归左右两边即可
你可能会产生疑惑:在进行正变换的时候我们先递归左右两边再考虑这一位,那么在逆变换的时候是否需要先考虑这一位再递归两边呢
而事实上因为每一位都是等价的,所以逆变换的时候一样可以先递归再考虑这一位,顺序无所谓
异或卷积:
异或这个东西和与还有或就不太一样……所以我们YY了挺长时间才YY明白异或卷积是个什么鬼畜
异或卷积基于如下原理:
定义i和j之间的奇偶性为:i&j中为1的位数的奇偶性,若为偶数则奇偶性是0,若为奇数则奇偶性是1
那么i和k的奇偶性异或j和k的奇偶性等于i^j和k的奇偶性
证明显然,YY一下即可
那么我们令数组a做正变换之后的数组b的意义是:b[i]= ∑a[j]−∑a[k] ∑ a [ j ] − ∑ a [ k ] (j满足i和j的奇偶性==0 ,k满足i和k的奇偶性==1)
那么如果要求a和b的异或卷积c,另a做完正变换为a’,b做完正变换为b’, c′[i]=a′[i]∗b′[i] c ′ [ i ] = a ′ [ i ] ∗ b ′ [ i ] ,那么c’恰好就是c做正变换之后的结果
证明显然,如果觉得不显然可以稍微YY一下就明白了
那么就考虑一下如何做正变换和逆变换就好了,还是按位分治
在正变换的时候,令
bn0[i]=b0[i]+b1[i],bn1[i]=b0[i]−b1[i]
b
n
0
[
i
]
=
b
0
[
i
]
+
b
1
[
i
]
,
b
n
1
[
i
]
=
b
0
[
i
]
−
b
1
[
i
]
在逆变换的时候,令
b0[i]=(bn0[i]+bn1[i])/2,b1[i]=(bn0[i]−bn1[i])/2
b
0
[
i
]
=
(
b
n
0
[
i
]
+
b
n
1
[
i
]
)
/
2
,
b
1
[
i
]
=
(
b
n
0
[
i
]
−
b
n
1
[
i
]
)
/
2
正确性显然
模板
void FWT(int a[],int n)
{
for(int d=1;d<n;d<<=1)
for(int m=d<<1,i=0;i<n;i+=m)
for(int j=0;j<d;j++)
{
int x=a[i+j],y=a[i+j+d];
a[i+j]=(x+y)%mod,a[i+j+d]=(x-y+mod)%mod;
//xor:a[i+j]=x+y,a[i+j+d]=x-y;
//and:a[i+j]=x+y;
//or:a[i+j+d]=x+y;
}
}
void UFWT(int a[],int n)
{
for(int d=1;d<n;d<<=1)
for(int m=d<<1,i=0;i<n;i+=m)
for(int j=0;j<d;j++)
{
int x=a[i+j],y=a[i+j+d];
a[i+j]=1LL*(x+y)*rev%mod,a[i+j+d]=(1LL*(x-y)*rev%mod+mod)%mod;
//xor:a[i+j]=(x+y)/2,a[i+j+d]=(x-y)/2;
//and:a[i+j]=x-y;
//or:a[i+j+d]=y-x;
}
}
void solve(int a[],int b[],int n)
{
FWT(a,n);
FWT(b,n);
for(int i=0;i<n;i++) a[i]=1LL*a[i]*b[i]%mod;
UFWT(a,n);
}