FWT 学习笔记&做题记录

前置知识

高维前缀和

多项式卷积


先来个模板:

P4717

FWT 用来解决像下面这样的卷积:

\[F(x)=\sum_{i\oplus j=x}A(i)B(j) \]

其中 \(\oplus\) 是三个位运算中的一个。

FWT 的时间复杂度为 \(O(n2^n)\),如果把 \(2^n\) 看成 \(n\),那么时间复杂度和 FFT,NTT 相同。

与卷积

\[F(x)=\sum_{i\ and\ j=x}A(i)B(j) \]

定义 \(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];
}

然后会发现一个惊人的式子:

\[F'(x)=A'(x)B'(x) \]

证明很简单,考虑后面的式子实际上就是

\[\sum_{x\in y_1}\sum_{x\in y_2}A(y_1)B(y_2) \]

然后会发现 \(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\) 取模,所以左右相等。

\[A'(x)B'(x)=(\sum_{i*x=0}A(i)-\sum_{i*x=1}A(i))(\sum_{j*x=0}B(j)-\sum_{j*x=1}B(j))\\ =(\sum_{i*x=0}A(i)\sum_{j*x=0}B(j))+(\sum_{i*x=1}A(i)\sum_{j*x=1}B(j))-(\sum_{i*x=1}A(i)\sum_{j*x=0}B(j))-(\sum_{i*x=0}A(i)\sum_{j*x=1}B(j))\\ =(\sum_{(i*x)\ xor \ (j*x)=0}A(i)B(j))-(\sum_{(i*x)\ xor \ (j*x)=1}A(i)B(j))\\ =(\sum_{x*(i\ xor \ j)=0}A(i)B(j))-(\sum_{x*(i\ xor \ j)=1}A(i)B(j))\\ =\sum_{x*y=0}F(y)-\sum_{x*y=1}F(y)=F'(x) \]

考虑咋实现,对于两个数差一个二进制位的数来说,实际上就是前面的加上后面的,后面的减去前面的。

逆变换也很简单,直接还原即可。

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;
            }
}

最后我们得到了 P4717代码


做题记录

P6097

给长度为 \(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\)

那么有一个想法就是加一维限制,限制其位数,对于每一个或卷积之后在进行一个多项式卷积。

\[C'(n,x)=\sum_{i=0}^n A'(i,x)B'(n-i,x) \]

然后对每一位进行还原即可,时间复杂度 \(O(n^22^n)\) 可过。

code


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\) 数量。

\[F(x)=\sum g(b_i\ xor \ x) \]

\(k_i=b_i\ xor \ x\)\(c_i\) 表示 \(b_i\) 的出现次数。

\[F(x)=\sum_{b_i\ xor \ x=k_i} g(k_i)=\sum_{b_i\ xor \ k_i=x} g(k_i)=\sum_{i\ xor \ j=x}g(i)c(j) \]

显然异或卷积,直接卷就好了。

code


P4221

题比较长,自己看吧

首先,判断一个点集是否合法可以直接暴力,只需要满足不连通或者存在奇点即可,这部分可以直接 \(O(n2^n)\)

直接考虑当前选了多少个点,令 \(f_{i,j}\) 表示已经选了 \(i\) 个点,集合为 \(j\) 的乘积,这明显是一个子集卷积,枚举上一轮选了多少个点,直接和上面预处理的结果卷起来就好了。

每一轮之后再去除以整个集合的和,也就是式子的下半部分。

这样一直做 \(n\) 轮就做完了,时间复杂度 \(O(n^22^n)\)

code


P8292

\(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})\),在民间数据中跑过了。

code

upd:棺方数据挂了,需要使用常数优化。

由于 \(43\times 47<2000\),所以 \(43\) 也可以分到后面,这样时间复杂度变成 \(O(2^{13}\sum {c_i})\)

注意需要特判 \(1849\),因为没有一个 \(<43\) 的因数可以除掉它。

code


posted @ 2022-04-15 08:29  houzhiyuan  阅读(238)  评论(0编辑  收藏  举报