FWT可以解决位运算卷积问题。

即\(h(i)=\sum\limits_{j⊕k=i} f(j)*g(k)\),其中“⊕”表示位运算。

与卷积:

定义\(f\)到\(F\)的变换:\(F(i)=\sum\limits_{j\&i==i}^{ }f(j)\)。

这样,若\(h(i)=\sum\limits_{j and k=i} f(j)*g(k)\),则\(H(i)=F(i)*G(i)\)。

变换方法:就是按照长度为\(2^i\)分段,把每段的后半部分加到前半部分(1对0有额外贡献)。

逆变换就是减回去。时间复杂度:\(O(nlogn)\)。

代码:

void fwtand(int sz[132000],int n)
{
for(int i=2;i<=n;i=(i<<1))
{
for(int j=0;j<n;j+=i)
{
for(int k=0;k<(i>>1);k++)
sz[j+k]=(sz[j+k]+sz[j+(i>>1)+k])%md;
}
}
}
void ifwtand(int sz[132000],int n)
{
for(int i=2;i<=n;i=(i<<1))
{
for(int j=0;j<n;j+=i)
{
for(int k=0;k<(i>>1);k++)
sz[j+k]=(sz[j+k]-sz[j+(i>>1)+k]+md)%md;
}
}
}

或卷积:

与“与卷积”类似。

定义\(f\)到\(F\)的变换:\(F(i)=\sum\limits_{j|i==i}^{ }f(j)\)。

这样,若\(h(i)=\sum\limits_{j or k=i} f(j)*g(k)\),则\(H(i)=F(i)*G(i)\)。

变换方法:就是按照长度为\(2^i\)分段,把每段的前半部分加到后半部分(0对1有额外贡献)。

逆变换就是减回去。时间复杂度:\(O(nlogn)\)。

代码:

void fwtor(int sz[132000],int n)
{
for(int i=2;i<=n;i=(i<<1))
{
for(int j=0;j<n;j+=i)
{
for(int k=0;k<(i>>1);k++)
sz[j+(i>>1)+k]=(sz[j+(i>>1)+k]+sz[j+k])%md;
}
}
}
void ifwtor(int sz[132000],int n)
{
for(int i=2;i<=n;i=(i<<1))
{
for(int j=0;j<n;j+=i)
{
for(int k=0;k<(i>>1);k++)
sz[j+(i>>1)+k]=(sz[j+(i>>1)+k]-sz[j+k]+md)%md;
}
}
}

这两个其实是高维前/后缀和

异或卷积:

这个比较常用。

定义\(f\)到\(F\)的变换:\(F(i)=\sum\limits_{j=0}^{2^n-1}(-1)^{bit(j and i)}f(j)\)。

这样,若\(h(i)=\sum\limits_{j xor k=i} f(j)*g(k)\),则\(H(i)=F(i)*G(i)\)。

变换方法:就是按照长度为\(2^i\)分段,把每段的前半部分变为前半部分加后半部分,

后半部分变为前半部分减后半部分。

逆变换就是相当于已知\(a+b=x,a-b=y\),则\(a=(x+y)/2,b=(x-y)/2\)。

就是正变换再除以2。

时间复杂度:\(O(nlogn)\)。

代码:

void fwtxor(int sz[132000],int n)
{
for(int i=2;i<=n;i=(i<<1))
{
for(int j=0;j<n;j+=i)
{
for(int k=0;k<(i>>1);k++)
{
int a=sz[j+k],b=sz[j+(i>>1)+k];
sz[j+k]=(a+b)%md;
sz[j+(i>>1)+k]=(a-b+md)%md;
}
}
}
}
void ifwtxor(int sz[132000],int n)
{
for(int i=2;i<=n;i=(i<<1))
{
for(int j=0;j<n;j+=i)
{
for(int k=0;k<(i>>1);k++)
{
int a=sz[j+k],b=sz[j+(i>>1)+k];
sz[j+k]=1ll*(a+b)*inv%md;
sz[j+(i>>1)+k]=1ll*(a-b+md)*inv%md;
}
}
}
}

FST:子集卷积

即\(h(i)=\sum\limits_{j or k=i且j and k=0} f(j)*g(k)\)。

比或卷积多了一个限制。

我们发现,设\(s(i)\)表示\(i\)的二进制表示中1的个数,那么如果\(i\|j=k,i\&j=0\),则\(s(i)+s(j)=s(k)\)。

利用这个性质,我们可以加一维表示\(s\),在\(F*G\)时考虑\(s\)的限制。

时间复杂度:\(O(nlog^2n)\)。

代码:

for(int i=0;i<len;i++)
{
for(int j=0;j<17;j++)
{
if(i&(1<<j))
sl[i]+=1;
}
}
for(int i=0;i<len;i++)
a[sl[i]][i]=sz[i];
for(int i=0;i<18;i++)
fwtor(a[i],len);
for(int i=0;i<18;i++)
{
for(int j=0;i+j<18;j++)
{
for(int k=0;k<len;k++)
h1[i+j][k]=(h1[i+j][k]+1ll*a[i][k]*a[j][k])%md;
}
}
for(int i=0;i<18;i++)
ifwtor(h1[i],len);
for(int i=0;i<len;i++)
ab[i]=h1[sl[i]][i];

例题:

CF914G

题意:

给你一个长度为\(n\)的数组\(s\).定义五元组\((a,b,c,d,e)\)是合法的当且仅当:

①.\(1\le a,b,c,d,e\le n\)

②.\((s_a|s_b)\&s_c\&(s_d\)^\(s_e)=2^i,i\in Z\)

③.\(s_a\&s_b=0\)

对于所有合法的五元组\((a,b,c,d,e)\)

求\(\sum f(s_a|s_b)*f(s_c)*f(s_d\)^\(s_e)\mod 10^9+7\)

\(f_0=0,f_1=1,f_i=f_{i-1}+f_{i-2}\)

\(1\le n\le10^6,0\le s_i\lt2^{17}\)

模板题。

先考虑\((s_a|s_b)\),发现是FST卷积。

再考虑\((s_d\)^\(s_e)\),是异或卷积。

然后就是与卷积了。

代码:

#include <stdio.h>
#define md 1000000007
#define inv 500000004
#define len 131072
int sz[132000],sl[132000];
void fwtor(int sz[132000],int n)
{
for(int i=2;i<=n;i=(i<<1))
{
for(int j=0;j<n;j+=i)
{
for(int k=0;k<(i>>1);k++)
sz[j+(i>>1)+k]=(sz[j+(i>>1)+k]+sz[j+k])%md;
}
}
}
void ifwtor(int sz[132000],int n)
{
for(int i=2;i<=n;i=(i<<1))
{
for(int j=0;j<n;j+=i)
{
for(int k=0;k<(i>>1);k++)
sz[j+(i>>1)+k]=(sz[j+(i>>1)+k]-sz[j+k]+md)%md;
}
}
}
void fwtand(int sz[132000],int n)
{
for(int i=2;i<=n;i=(i<<1))
{
for(int j=0;j<n;j+=i)
{
for(int k=0;k<(i>>1);k++)
sz[j+k]=(sz[j+k]+sz[j+(i>>1)+k])%md;
}
}
}
void ifwtand(int sz[132000],int n)
{
for(int i=2;i<=n;i=(i<<1))
{
for(int j=0;j<n;j+=i)
{
for(int k=0;k<(i>>1);k++)
sz[j+k]=(sz[j+k]-sz[j+(i>>1)+k]+md)%md;
}
}
}
void fwtxor(int sz[132000],int n)
{
for(int i=2;i<=n;i=(i<<1))
{
for(int j=0;j<n;j+=i)
{
for(int k=0;k<(i>>1);k++)
{
int a=sz[j+k],b=sz[j+(i>>1)+k];
sz[j+k]=(a+b)%md;
sz[j+(i>>1)+k]=(a-b+md)%md;
}
}
}
}
void ifwtxor(int sz[132000],int n)
{
for(int i=2;i<=n;i=(i<<1))
{
for(int j=0;j<n;j+=i)
{
for(int k=0;k<(i>>1);k++)
{
int a=sz[j+k],b=sz[j+(i>>1)+k];
sz[j+k]=1ll*(a+b)*inv%md;
sz[j+(i>>1)+k]=1ll*(a-b+md)*inv%md;
}
}
}
}
int a[18][132000],h1[18][132000],fib[132000],h2[132000];
int ab[132000],de[132000],ans[132000];
int main()
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++)
{
int a;
scanf("%d",&a);
sz[a]+=1;
}
for(int i=0;i<len;i++)
{
for(int j=0;j<17;j++)
{
if(i&(1<<j))
sl[i]+=1;
}
}
fib[0]=0;fib[1]=1;
for(int i=2;i<len;i++)
fib[i]=(fib[i-1]+fib[i-2])%md;
for(int i=0;i<len;i++)
a[sl[i]][i]=sz[i];
for(int i=0;i<18;i++)
fwtor(a[i],len);
for(int i=0;i<18;i++)
{
for(int j=0;i+j<18;j++)
{
for(int k=0;k<len;k++)
h1[i+j][k]=(h1[i+j][k]+1ll*a[i][k]*a[j][k])%md;
}
}
for(int i=0;i<18;i++)
ifwtor(h1[i],len);
for(int i=0;i<len;i++)
ab[i]=h1[sl[i]][i];
for(int i=0;i<len;i++)
de[i]=sz[i];
fwtxor(de,len);
for(int i=0;i<len;i++)
de[i]=1ll*de[i]*de[i]%md;
ifwtxor(de,len);
for(int i=0;i<len;i++)
{
ab[i]=1ll*ab[i]*fib[i]%md;
sz[i]=1ll*sz[i]*fib[i]%md;
de[i]=1ll*de[i]*fib[i]%md;
}
fwtand(ab,len);
fwtand(sz,len);
fwtand(de,len);
for(int i=0;i<len;i++)
ans[i]=1ll*ab[i]*sz[i]%md*de[i]%md;
ifwtand(ans,len);
int jg=0;
for(int i=1;i<=len;i=(i<<1))
jg=(jg+ans[i])%md;
printf("%d",jg);
return 0;
}

uoj310【UNR #2】黎明前的巧克力

题意:有一个集合,选出两个不相交的子集,使其异或和相等,问方案数。

考虑dp:设\(dp(i,j)\)表示考虑到i,两人异或为j的方案数。

则\(dp(i,j)=dp(i-1,j)+2*dp(i-1,j\)^\(a(i))\)。

考虑FWT:对每个i构造A,使\(A(0)=1,A(a(i))=2\)。

对每个A做FWT,乘起来后再IFWT。但是复杂度太高。

根据公式,可以发现,FWT(A)的每位只能是3或-1。

那么,只要知道FWT后,A的每个对应位置之和,就能解出3和-1的数量了,之后快速幂即可。

根据加法的运算律,可以得知若干个长度相等的序列FWT后对应位置求和,等于先求和,再FWT。

所以求和后,FWT,之后快速幂算出每个位置的值,再IFWT,最后位置0的值减1就是答案。

时间复杂度:\(O(mlogm)\)。

思路非常巧妙。

代码:

#include <stdio.h>
#define md 998244353
#define inv 499122177
#define len 1048576
void fwt(int sz[1050000],int n)
{
for(int i=2;i<=n;i=(i<<1))
{
int t=(i>>1);
for(int j=0;j<n;j+=i)
{
for(int k=j;k<j+t;k++)
{
int a=sz[k+t];
sz[k+t]=sz[k]-a;
sz[k]=sz[k]+a;
}
}
}
}
void ifwt(int sz[1050000],int n)
{
for(int i=2;i<=n;i=(i<<1))
{
int t=(i>>1);
for(int j=0;j<n;j+=i)
{
for(int k=j;k<j+t;k++)
{
int a=sz[k+t];
sz[k+t]=1ll*(sz[k]-a+md)*inv%md;
sz[k]=1ll*(sz[k]+a)*inv%md;
}
}
}
}
int sz[1050010],mi[1050000],m3[1050010],sl[1050000];
int main()
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",&sz[i]);
sl[0]=n;
for(int i=0;i<n;i++)
sl[sz[i]]+=2;
fwt(sl,len);
for(int i=0;i<len;i++)
mi[i]=(n+sl[i])/4;
m3[0]=1;
for(int i=1;i<=n;i++)
m3[i]=3ll*m3[i-1]%md;
for(int i=0;i<len;i++)
{
if((n-mi[i])%2==0)
mi[i]=m3[mi[i]];
else
mi[i]=md-m3[mi[i]];
}
ifwt(mi,len);
printf("%d",(mi[0]-1+md)%md);
return 0;
}

扩展

这个技巧还可以扩展:

就是说当权值有k个时,我们先将其中一个权值变为0,这样总共可能的贡献有\(2^{k-1}\)种。(每个系数是1或-1),0的贡献一定是1。

为了把这\(2^{k-1}\)种的数量分别求出来,我们需要找\(2^{k-1}\)个等式。

可以枚举剩余k-1个元素的子集,只将其异或的位置+1,做FWT。

这样,每个位置,会得到\(2^{k-1}\)个数。将这些数做FWT后,就可以的得到\(2^{k-1}\)种可能分别的数量,快速幂即可。

证明略。

(核心)代码:

int xo=0,mi=1;
for(int i=0;i<n;i++)
{
for(int j=0;j<k;j++)
scanf("%d",&p[i][j]);
xo^=p[i][0];
for(int j=1;j<k;j++)
p[i][j]^=p[i][0];
}
ans[xo]=1;
fwtxor(ans,(1<<m));
for(int s=0;s<(1<<(k-1));s++)
{
for(int i=0;i<n;i++)
{
int z=0;
for(int j=1;j<k;j++)
{
if(s&(1<<(j-1)))
z^=p[i][j];
} sz[z]+=1;
}
fwtxor(sz,(1<<m));
for(int i=0;i<(1<<m);i++)
{ nf[(i<<(k-1))|s]=sz[i];
sz[i]=0;
}
}
for(int i=0;i<(1<<m);i++)
{
for(int s=0;s<(1<<(k-1));s++)
zz[s]=nf[(i<<(k-1))|s];
ifwtxor(zz,1<<(k-1));
for(int s=0;s<(1<<(k-1));s++)
{
int he=sl[0];
for(int j=1;j<k;j++)
{
if(s&(1<<(j-1)))
he=(he-sl[j]+md)%md;
else
he=(he+sl[j])%md;
}
ans[i]=1ll*ans[i]*ksm(he,zz[s])%md;
}
}
ifwtxor(ans,(1<<m));

CF662C Binary Table

题意:

有一个 n 行 m 列的表格,每个元素都是 0/1 ,每次操作可以选择一行或一列,把 0/1 翻转,即把 0 换为 1 ,把 1 换为 0 。请问经过若干次操作后,表格中最少有多少个 1。\((1\leq n \leq 20,1\leq m \leq 10^5)\)。

首先,我们可以枚举行的交换,共\(2^n\)种。

然后,对每一列,考虑它是否交换。复杂度为\(O(nm2^n)\)。

考虑优化:

首先,我们发现,如果记翻转为1,那么翻转就是异或。

记B数组表示状态压缩后的每列的出现次数。

记A数组表示一列为这个状态的1的最少个数。

那么,设\(C_i=A_{ixorj}*B_j\)之和,那么C的最小值就是答案。

反一下,将A和B做异或卷积,即可得到C。时间复杂度\(O(nm+n2^n)\)。

代码:

#include <stdio.h>
#define ll long long
void fwt(ll sz[1048576],int n)
{
for(int h=2;h<=n;h=(h<<1))
{
for(int i=0;i<n;i+=h)
{
for(int j=0;j<(h>>1);j++)
{
ll a=sz[i+j],b=sz[i+j+(h>>1)];
sz[i+j]=a+b;
sz[i+j+(h>>1)]=a-b;
}
}
}
}
void ifwt(ll sz[1048576],int n)
{
for(int h=2;h<=n;h=(h<<1))
{
for(int i=0;i<n;i+=h)
{
for(int j=0;j<(h>>1);j++)
{
ll a=sz[i+j],b=sz[i+j+(h>>1)];
sz[i+j]=(a+b)/2;
sz[i+j+(h>>1)]=(a-b)/2;
}
}
}
}
int sz[20][100005];char zf[100005];
ll sa[1048576],sb[1048576];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
{
scanf("%s",zf);
for(int j=0;j<m;j++)
sz[i][j]=zf[j]-'0';
}
for(int i=0;i<(1<<n);i++)
{
int s=0;
for(int j=0;j<n;j++)
{
if(i&(1<<j))
s+=1;
}
sa[i]=n-s;
if(s<sa[i])sa[i]=s;
}
for(int i=0;i<m;i++)
{
int s=0;
for(int j=0;j<n;j++)
{
if(sz[j][i])
s|=(1<<j);
}
sb[s]+=1;
}
fwt(sa,1<<n);fwt(sb,1<<n);
for(int i=0;i<(1<<n);i++)
sa[i]*=sb[i];
ifwt(sa,1<<n);
int ans=99999999;
for(int i=0;i<(1<<n);i++)
{
if(sa[i]<ans)
ans=sa[i];
}
printf("%d",ans);
return 0;
}

FWT 等总结 题解的更多相关文章

  1. $FFT/NTT/FWT$题单&简要题解

    打算写一个多项式总结. 虽然自己菜得太真实了. 好像四级标题太小了,下次写博客的时候再考虑一下. 模板 \(FFT\)模板 #include <iostream> #include < ...

  2. 【题解】#6622. 「THUPC 2019」找树 / findtree(Matrix Tree+FWT)

    [题解]#6622. 「THUPC 2019」找树 / findtree(Matrix Tree+FWT) 之前做这道题不理解,有一点走火入魔了,甚至想要一本近世代数来看,然后通过人类智慧思考后发现, ...

  3. 【题解】毒蛇越狱(FWT+容斥)

    [题解]毒蛇越狱(FWT+容斥) 问了一下大家咋做也没听懂,按兵不动没去看题解,虽然已经晓得复杂度了....最后感觉也不难 用FWT_OR和FWT_AND做一半分别求出超集和和子集和,然后 枚举问号是 ...

  4. 福建工程学院第十四届ACM校赛M题题解 fwt进阶,手推三进制fwt

    第九集,结束亦是开始 题意: 大致意思就是给你n个3进制的数字,让你计算有多少对数字的哈夫曼距离等于i(0<=i<=2^m) 思路: 这个是一个防ak题,做法是要手推公式的fwt 大概就这 ...

  5. CROC 2016 - Final Round [Private, For Onsite Finalists Only] C. Binary Table FWT

    C. Binary Table 题目连接: http://codeforces.com/problemset/problem/662/C Description You are given a tab ...

  6. 卷积FFT、NTT、FWT

    先简短几句话说说FFT.... 多项式可用系数和点值表示,n个点可确定一个次数小于n的多项式. 多项式乘积为 f(x)*g(x),显然若已知f(x), g(x)的点值,O(n)可求得多项式乘积的点值. ...

  7. HDU 5823 color II(FWT)

    [题目链接] http://acm.hdu.edu.cn/showproblem.php?pid=5823 [题目大意] 定义一张无向图的价值:给每个节点染色使得每条边连接的两个节点颜色不相同的最少颜 ...

  8. SDOI2017 Round2 详细题解

    这套题实在是太神仙了..做了我好久...好多题都是去搜题解才会的 TAT. 剩的那道题先咕着,如果省选没有退役就来填吧. 「SDOI2017」龙与地下城 题意 丢 \(Y\) 次骰子,骰子有 \(X\ ...

  9. UOJ#310.【UNR #2】黎明前的巧克力(FWT)

    题意 给出 \(n\) 个数 \(\{a_1, \cdots, a_n\}\),从中选出两个互不相交的集合(不能都为空),使得第一个集合与第二个集合内的数的异或和相等,求总方案数 \(\bmod 99 ...

随机推荐

  1. WUSTOJ 1208: 计算整数四则运算表达式的结果(Java)

    1208: 计算整数四则运算表达式的结果 参考资料 数据结构(C语言版)严蔚敏 吴伟民 编著----表达式求值 题目   简单四则运算.更多内容点击标题. 保证表达式合法. 运算符只包含:加(+),减 ...

  2. 关于OI中的各种数学

    学到后面数学越来越多了,感觉好难啊,开个博客专门记录一下数学相关的东西 因为反正也没人看,所以主要还是给自己看的 一些符号: 数论函数的卷积:$\ast$,$ h = f \ast g$ 则 $h(n ...

  3. Golang语言编程规范

    Golang语言编程规范 一.说明 编程规范好,可避免语言陷阱,可有利团队协作,有利项目维护. 正常的Go编程规范有两种:编译器强制的(必须的),gofmt格式化非强制的(非必须). Go宣告支持驼峰 ...

  4. Ubuntu16.04安装kubernetes1.13集群

    Ubuntu16.04安装kubernetes1.13集群 最新的安装可以使用以下方式:https://www.cnrancher.com/docs/rancher/v2.x/cn/overview/ ...

  5. Java内存 模型理解

    概述 在正式讲Java内存模型之前,我们先了解一些物理计算机并发问题,然后一点点的引出Java内存模型的由来. 多任务处理在现在计算机操作系统中几乎是一项必备的功能.这不单是因为计算机计算能力强大,更 ...

  6. 九、小程序 Redux详解与在小程序中怎么使用(action和reducers)

    什么是Redux ​ Redux我们可以把它理解成一个状态管理器,可以把状态(数据)存在Redux中,以便增.删.改.例如: 从服务器上取一个收藏列表,就可以把取回来的列表数据用Redux管理,多个页 ...

  7. 关于mysql 自增长 主键

    我遇到过 我这样建表 ![](http://images2017.cnblogs.com/blog/1128666/201709/1128666-20170917222547078-151122413 ...

  8. 【多进程】php实现 master-worker 守护多进程模式

    <?php class Worker{ public static $count = 2; public static function runAll(){ static::runMaster( ...

  9. php获取客户机mac地址

    @exec("arp -a",$array); //执行arp -a命令,结果放到数组$array中 foreach($array as $value){ //匹配结果放到数组$m ...

  10. 【转载】salesforce 零基础开发入门学习(一)Salesforce功能介绍,IDE配置以及资源下载

    salesforce 零基础开发入门学习(一)Salesforce功能介绍,IDE配置以及资源下载   目前国内已经有很多公司做salesforce,但是国内相关的资料确是少之又少.上个月末跳槽去了新 ...