FWT 学习笔记&做题记录
前置知识
先来个模板:
FWT 用来解决像下面这样的卷积:
其中 \(\oplus\) 是三个位运算中的一个。
FWT 的时间复杂度为 \(O(n2^n)\),如果把 \(2^n\) 看成 \(n\),那么时间复杂度和 FFT,NTT 相同。
与卷积
定义 \(A'(x)\) 表示 \(\sum_{x\in y}A(y)\),这是一个 FWT 变换,显然这个变换可以直接高维前缀和。
void FMT(int *A,int *ans){
for(int i=0;i<S;i++)ans[i]=A[i];
for(int i=0;i<n;i++)
for(int j=S-1;j>=0;j--)
if(j&(1<<i))ans[j^(1<<i)]+=ans[j];
}
然后会发现一个惊人的式子:
证明很简单,考虑后面的式子实际上就是
然后会发现 \(x\in (y_1\cap y_2)\),所以这样计算出来的显然和右边相等。
然后考虑咋从 \(F'(x)\) 变成 \(F(x)\)。
实际上就是再减回去,把加号变成减号即可。
void IFMT(int *A,int *ans){
for(int i=0;i<S;i++)ans[i]=A[i];
for(int i=0;i<n;i++)
for(int j=S-1;j>=0;j--)
if(j&(1<<i))ans[j^(1<<i)]-=ans[j];
}
然后你就会发现,两个的区别只有一个符号,可以直接带一个 \(T\) 进去。
卷积写法和 FFT 差不多,就是:
FWT(A,A,1),FWT(B,B,1);
for(int i=0;i<S;i++)ans[i]=A[i]*B[i];
FWT(ans,ans,-1);
和卷积就好了。
或卷积
和上面的差不多,可以发现本质上就是枚举顺序换一下就好了。
关于写法
FWT 写法可以和 FFT 长得一样,这样看上去更加优美。
//和卷积
void FMT_And(int *A,int T){
for(int len=1;len<1<<n;len<<=1)
for(int l=0;l<1<<n;l+=(len<<1))
for(int k=0;k<len;k++)
A[l+k]=A[l+k]+A[l+k+len]*T;
}
//或卷积
void FMT_Or(int *A,int T){
for(int len=1;len<1<<n;len<<=1)
for(int l=0;l<1<<n;l+=(len<<1))
for(int k=0;k<len;k++)
A[l+k+len]=A[l+k]*T+A[l+k+len];
}
可以发现本质上 \(len\) 在枚举哪一位,\(l\) 在枚举这一位前面的,\(k\) 是后面的,然后就和上面的写法一样了。
这样写的好处在于,无论什么运算,只需要在两个数的情况下满足,就可以直接写在里面。
异或卷积
最后讲是因为实在太难了。
神仙构造:\(F'(x)=\sum_{i*x=0}F(i)-\sum_{i*x=1}F(i)\)。
令 \(p(x)\) 表示 \(x\) 二进制位上 \(1\) 的个数,则上面 \(*\) 表示 \(p(i\& x)\mod 2\)。
可以发现 \((i*x)\ xor \ (i*y)=i*(x\ xor \ y)\)。
证明很简单,按位考虑,如果 \(i\) 这位是 \(0\),那么左右都是 \(0\),如果 \(i\) 是 \(1\),那么 \(x,y\) 如果有 \(1\) 就会产生 \(1\) 的贡献,又因为有对 \(2\) 取模,所以左右相等。
考虑咋实现,对于两个数差一个二进制位的数来说,实际上就是前面的加上后面的,后面的减去前面的。
逆变换也很简单,直接还原即可。
void FMT_Xor(int *A,int T){
for(int len=1;len<1<<n;len<<=1)
for(int l=0;l<1<<n;l+=(len<<1))
for(int k=0;k<len;k++){
int x=A[l+k],y=A[l+k+len];
A[l+k]=x+y,A[l+k+len]=x-y;
if(T==-1)A[l+k]=A[l+k]/2,A[l+k+len]=A[l+k+len]/2;
}
}
做题记录
给长度为 \(2^n\) 的两个序列 \(A,B\),求其子集卷积结果。
\[c_k=\sum_{\substack{{i \& j=0}\\{i~\mid~ j=k}}} a_i b_j \]
\(n\le 20\)。
一个模板题。
考虑 \(i \& j=0\) 的限制比较阴间,没有这个限制的话可以直接或卷积。
令 \(p(x)\) 表示 \(x\) 二进制位上 \(1\) 的个数,那么可以发现,\(p(i)+p(j)\ge p(i|j)\),仅当 \(p(i)+p(j)=p(i|j)\) 时 \(i\& j=0\)。
那么有一个想法就是加一维限制,限制其位数,对于每一个或卷积之后在进行一个多项式卷积。
即
然后对每一位进行还原即可,时间复杂度 \(O(n^22^n)\) 可过。
CF662C *2600
tourist 场上没做出的题被我做出了。
有一个 \(n\) 行 \(m\) 列的表格,每个元素都是 \(0/1\) ,每次操作可以选择一行或一列,把 \(0/1\) 翻转,即把 \(0\) 换为 \(1\) ,把 \(1\) 换为 \(0\) 。请问经过若干次操作后,表格中最少有多少个 \(1\) 。
\(n\le 20,m\le 10^5\)。
\(n\) 这么小,显然先把每一列状压一下,假设状压结果是 \(b_i\)。
枚举当前行上的操作 \(x\),令 \(g(x)\) 表示 \(\min(p(x),n-p(x))\),即通过翻转得到的最小 \(1\) 数量。
令 \(k_i=b_i\ xor \ x\),\(c_i\) 表示 \(b_i\) 的出现次数。
显然异或卷积,直接卷就好了。
题比较长,自己看吧
首先,判断一个点集是否合法可以直接暴力,只需要满足不连通或者存在奇点即可,这部分可以直接 \(O(n2^n)\)。
直接考虑当前选了多少个点,令 \(f_{i,j}\) 表示已经选了 \(i\) 个点,集合为 \(j\) 的乘积,这明显是一个子集卷积,枚举上一轮选了多少个点,直接和上面预处理的结果卷起来就好了。
每一轮之后再去除以整个集合的和,也就是式子的下半部分。
这样一直做 \(n\) 轮就做完了,时间复杂度 \(O(n^22^n)\)。
有 \(n\) 个数 \(a_i\) ,\(Q\) 次询问,每次询问给出 \(k\) 个质数,可以在 \(n\) 个数中选择若干个数,使得 \(k\) 个质数都能整除选出得数的乘积,求方案数对 \(998244353\)。
\(n\le 10^6,\sum k\le 18000,a_i\le 2000\)。
发现 \(a_i\le 2000\),容易想到根号分治,然后可以发现 \(\le \sqrt{2000}\) 的质数只有 \(14\) 个,可以状压。
先不考虑 \(> \sqrt{2000}\) 的,可以发现这本质上就是个或卷积,设 \(f(x)\) 表示选了集合为 \(x\) 的因数的方案数,答案相当于把 \(n\) 个数每个数的 \(f\) 数组卷起来。
如何快速卷?把每个数组都 FWT,可以发现本质是一个其他都是 \(1\),包含 \(a_i\) 的数是 \(2\) 的东西,所以可以全部加起来,然后 FWT 之后求一个 \(2\) 的幂次。
最后的答案相当于 IFWT 之后求一个后缀和。
然后考虑 \(>\sqrt{2000}\) 的因数咋做,考虑分组,每个数分到自己 \(>\sqrt{2000}\) 的因数的组里去,最后相当于每个组卷起来,如果一个组被选中,那么就相当于去掉一个都不选的方案,也就是减一。
时间复杂度 \(O(2^{14}\sum {c_i})\),在民间数据中跑过了。
upd:棺方数据挂了,需要使用常数优化。
由于 \(43\times 47<2000\),所以 \(43\) 也可以分到后面,这样时间复杂度变成 \(O(2^{13}\sum {c_i})\)。
注意需要特判 \(1849\),因为没有一个 \(<43\) 的因数可以除掉它。