引入
有的时候我们需要进行这样的求和:
其中
为二元运算
之一,即位运算卷积.
暴力显然是
,我们可不可以用类似
的思想,把
转化为
(转点值),然后令
,然后再对
进行求逆呢?(由点值求系数) 答案是肯定的!
这样的正逆变换 称为 快速沃尔什变换.
令
.
我们把每个二进制看做一维的话,就是一个高维前缀和啦~~
设 表示 前后长度为 的系数子序列,令 表示对应位置相加, 表示序列相接,则有.
.
void fwt_or(int *f) {
for(int k=1;k<n;k*=2)//维度
for(int i=0;i<n;i+=2*k)
for(int j=0;j<k;j++)
(f[i+j+k] += f[i+j]) %= mod;
}
现在需要证明的是
逆变换的时候,只要把正变换的影响消去即可.
.
void ifwt_or(int *f) {
for(int k=n/2;k;k/=2)
for(int i=0;i<n;i+=2*k)
for(int j=0;j<k;j++)
f[i+j+k] = (f[i+j+k]-f[i+j]+mod)%mod;
}
你可能觉得这样的话,上面的
必须从
开始
,其实从
开始结果是一样的.
因为你把高低位互换不影响变换的正确性,这个东西在后面都适用,所以两个代码可以合并.
void fwt_or(int *f,ll x) {
if(x==-1) x+=mod;
for(int k=1;k<n;k*=2)
for(int i=0;i<n;i+=2*k)
for(int j=0;j<k;j++)
add(f[i+j+k],f[i+j]*x%mod);
}
同理,设
.
因为
,所以同理可证转点值后相乘的结果正确.
正逆变化类似.
void fwt_and(int *f,ll x) {
if(x==-1) x+=mod;
for(int k=1;k<n;k*=2)
for(int i=0;i<n;i+=2*k)
for(int j=0;j<k;j++)
add(f[i+j],f[i+j+k]*x%mod);
}
设
表示
二进制下有多少个1.
定义
.
则
.
性质:
.
证明:
为不进位加法,所以我们实际上是证明
我们对一个位的所有情况进行证明,那么总体就一定满足.
0 | - | - |
1 | 0 | 1 |
1 | 1 | 1 |
显然都是0.
,则显然成立.
,左边为2,右边为0,成立!
综上:我们通过穷举证明了每一位的情况,也就是证明了所有的情况都满足.
现在依然是要证明:
正变换:
.
证明:在求解小规模数据时
时不知道自己最高位位1的,
此时,
.
,所以右边合并的时候
的符号改变.
逆变换:
.
void fwt_xor(int *f,ll x) {
if(x==-1) x=(mod+1)/2;
for(int k=1;k<n;k*=2)
for(int i=0;i<n;i+=2*k)
for(int j=0;j<k;j++) {
int u=f[i+j],v=f[i+j+k];
add(f[i+j],v); del(f[i+j+k]=u,v);
f[i+j] = f[i+j]*x%mod;
f[i+j+k] = f[i+j+k]*x%mod;
}
}
板子:
int n,a[N],b[N],A[N],B[N];
void add(int &x,int y) {x+=y; if(x>=mod) x-= mod;}
void upd(int &x) {x+=x>>31&mod;}
void del(int &x,int y) {upd(x-=y);}
void fwt_or(int *f,ll x) {
if(x==-1) x+=mod;
for(int k=1;k<n;k*=2)
for(int i=0;i<n;i+=2*k)
for(int j=0;j<k;j++)
add(f[i+j+k],f[i+j]*x%mod);
}
void fwt_and(int *f,ll x) {
if(x==-1) x+=mod;
for(int k=1;k<n;k*=2)
for(int i=0;i<n;i+=2*k)
for(int j=0;j<k;j++)
add(f[i+j],f[i+j+k]*x%mod);
}
void fwt_xor(int *f,ll x) {
if(x==-1) x=(mod+1)/2;
for(int k=1;k<n;k*=2)
for(int i=0;i<n;i+=2*k)
for(int j=0;j<k;j++) {
int u=f[i+j],v=f[i+j+k];
add(f[i+j],v); del(f[i+j+k]=u,v);
f[i+j] = f[i+j]*x%mod;
f[i+j+k] = f[i+j+k]*x%mod;
}
}
void solve(void (*fwt)(int*f,ll x)) {
for(int i=0;i<n;i++) a[i]=A[i],b[i]=B[i];
fwt(a,1); fwt(b,1);
for(int i=0;i<n;i++) a[i]=(ll)a[i]*b[i]%mod;
fwt(a,-1);
for(int i=0;i<n;i++) pr1(a[i]);
puts("");
}
int main() {
qr(n); n=1<<n;
for(int i=0;i<n;i++) qr(A[i]);
for(int i=0;i<n;i++) qr(B[i]);
solve(fwt_or);
solve(fwt_and);
solve(fwt_xor);
return 0;
}
例题
bzoj #4589. Hard Nim
有两个神在玩nim游戏,有 堆石子,每堆石子的大小为 的质数,求先手必败的方案数.
.
定义一个多项式
定义
表示
对应位置相乘,区别于
(表示卷积).
则我们要求的就是
.
因为每乘一次就相当于作一次异或卷积,即多一堆石子,所以正确.
复杂度为 .
void add(int &x,int y) {x+=y; if(x>=mod) x-=mod;}
void upd(int &x) {x+=x>>31&mod;}
void del(int &x,int y) {upd(x -= y);}
int prime[N],tot; bool v[N];
void get(int x) {
for(int i=2;i<=x;i++) {
if(!v[i]) prime[++tot]=i;
for(int j=1,k;(k=i*prime[j])<=x;j++) {
v[k]=1;
if(i%prime[j]==0) break;
}
}
}
int t;
void fwt(int *f,ll x) {
if(x==-1) x=(mod+1)/2;
for(int k=1;k<t;k*=2)
for(int i=0;i<t;i+=2*k)
for(int j=0;j<k;j++) {
int u=f[i+j],v=f[i+j+k];
add(f[i+j],v); del(f[i+j+k]=u,v);
f[i+j]=f[i+j]*x%mod;
f[i+j+k]=f[i+j+k]*x%mod;
}
}
int n,m,f[N];
ll power(ll a,ll b=n) {
ll c=1;
while(b) {
if(b&1) c=c*a%mod;
b /= 2; a=a*a%mod;
}
return c;
}
int main() {
get(N-1);
while(~scanf("%d%d",&n,&m)) {
for(t=1;t<=m;t*=2);
memset(f,0,sizeof f);
for(int i=1;i<=tot&&prime[i]<=m;i++) f[prime[i]]=1;;
fwt(f,1);
for(int i=0;i<t;i++) f[i]=power(f[i]);
fwt(f,-1); pr2(f[0]);
}
return 0;
}
P3175 [HAOI2015]按位或
定义
为取到
集合中任意一位的最小时间.
#include<bits/stdc++.h>
using namespace std;
const int N=(1<<20)|10;
const double eps=1e-9;
int n,g[N];
double f[N],ans;
void fwt(double *f) {
for(int k=1;k<n;k*=2)
for(int i=0;i<n;i+=2*k)
for(int j=0;j<k;j++)
f[i+j+k] += f[i+j];
}
int main() {
scanf("%d",&n); n=1<<n;
for(int i=0;i<n;i++) scanf("%lf",&f[i]);
fwt(f); g[0]=-1;
for(int i=1;i<n;i++) {
g[i]=-g[i&(i-1)];
if(fabs(f[i^(n-1)]-1)<eps)
{puts("INF"); return 0;}
ans += g[i]/(1-f[i^(n-1)]);
}
printf("%.10lf\n",ans);
return 0;
}