FWT 学习笔记&做题记录

前置知识

高维前缀和

多项式卷积


先来个模板:

P4717

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

F(x)=ij=xA(i)B(j)

其中 是三个位运算中的一个。

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

与卷积

F(x)=i and j=xA(i)B(j)

定义 A(x) 表示 xyA(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)

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

xy1xy2A(y1)B(y2)

然后会发现 x(y1y2),所以这样计算出来的显然和右边相等。

然后考虑咋从 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)=ix=0F(i)ix=1F(i)

p(x) 表示 x 二进制位上 1 的个数,则上面 表示 p(i&x)mod2

可以发现 (ix) xor (iy)=i(x xor y)

证明很简单,按位考虑,如果 i 这位是 0,那么左右都是 0,如果 i1,那么 x,y 如果有 1 就会产生 1 的贡献,又因为有对 2 取模,所以左右相等。

A(x)B(x)=(ix=0A(i)ix=1A(i))(jx=0B(j)jx=1B(j))=(ix=0A(i)jx=0B(j))+(ix=1A(i)jx=1B(j))(ix=1A(i)jx=0B(j))(ix=0A(i)jx=1B(j))=((ix) xor (jx)=0A(i)B(j))((ix) xor (jx)=1A(i)B(j))=(x(i xor j)=0A(i)B(j))(x(i xor j)=1A(i)B(j))=xy=0F(y)xy=1F(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

给长度为 2n 的两个序列 A,B,求其子集卷积结果。

ck=i&j=0i  j=kaibj

n20

一个模板题。

考虑 i&j=0 的限制比较阴间,没有这个限制的话可以直接或卷积。

p(x) 表示 x 二进制位上 1 的个数,那么可以发现,p(i)+p(j)p(i|j),仅当 p(i)+p(j)=p(i|j)i&j=0

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

C(n,x)=i=0nA(i,x)B(ni,x)

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

code


CF662C *2600

tourist 场上没做出的题被我做出了

有一个 nm 列的表格,每个元素都是 0/1 ,每次操作可以选择一行或一列,把 0/1 翻转,即把 0 换为 1 ,把 1 换为 0 。请问经过若干次操作后,表格中最少有多少个 1
n20,m105

n 这么小,显然先把每一列状压一下,假设状压结果是 bi

枚举当前行上的操作 x,令 g(x) 表示 min(p(x),np(x)),即通过翻转得到的最小 1 数量。

F(x)=g(bi xor x)

ki=bi xor xci 表示 bi 的出现次数。

F(x)=bi xor x=kig(ki)=bi xor ki=xg(ki)=i xor j=xg(i)c(j)

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

code


P4221

题比较长,自己看吧

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

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

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

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

code


P8292

n 个数 aiQ 次询问,每次询问给出 k 个质数,可以在 n 个数中选择若干个数,使得 k 个质数都能整除选出得数的乘积,求方案数对 998244353
n106,k18000,ai2000

发现 ai2000,容易想到根号分治,然后可以发现 2000 的质数只有 14 个,可以状压。

先不考虑 >2000 的,可以发现这本质上就是个或卷积,设 f(x) 表示选了集合为 x 的因数的方案数,答案相当于把 n 个数每个数的 f 数组卷起来。

如何快速卷?把每个数组都 FWT,可以发现本质是一个其他都是 1,包含 ai 的数是 2 的东西,所以可以全部加起来,然后 FWT 之后求一个 2 的幂次。

最后的答案相当于 IFWT 之后求一个后缀和。

然后考虑 >2000 的因数咋做,考虑分组,每个数分到自己 >2000 的因数的组里去,最后相当于每个组卷起来,如果一个组被选中,那么就相当于去掉一个都不选的方案,也就是减一。

时间复杂度 O(214ci),在民间数据中跑过了。

code

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

由于 43×47<2000,所以 43 也可以分到后面,这样时间复杂度变成 O(213ci)

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

code


posted @   houzhiyuan  阅读(292)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
点击右上角即可分享
微信分享提示
主题色彩