FWT

引入

已知 \(A,B\),用于解决 \(C_i=\sum_{j\star k=i}A_jB_k\)。其中 \(\star\) 是一种二进制运算。
我们可以构造 \(fwt\) 序列,
使得 \(A\to fwt_A\)\(B\to fwt_B\)\(fwt_C\)\(fwt_A,fwt_B\) 对应位相乘,再 \(fwt_C\to C\)

OR

显然有 \(j|i=i,k|i=i\),则 \((j|k)|i=i\)
构造 \(fwt_A[i]=\sum_{j|i=i}A_j\)
那么 \(fwt_A[i]\times fwt_B[i]=(\sum_{j|i=i}A_j)\times (\sum_{k|i=i} B_k)\)
\(=\sum_{(j|k)|i=i} A_jB_k=fwt_C[i]\).

\(A\to fwt_A\),设 \(merge\) 表示两个序列从左到右拼起来。
初始地,\(fwt_A[i]=A[i]\),考虑分治,设左边的 \(fwt_0\),右边的是 \(fwt_1\)
那么 \(fwt=merge(fwt_0,fwt_0+fwt_1)\)

\(fwt_A\to A\)
\(A=merge(A_0,A_1-A_0)\)。可以看做只是翻转符号。

AND

同理。

XOR

定义 \(x\star y=popcount(x\& y)\bmod 2\)。那么,\((x\star y)\text{xor}(x\star z)=x\star(y\space\text{xor}\space z)\)
构造 \(fwt_A[i]=\sum_{i\star j=0} A_j-\sum_{i\star j=1}A_j\)
那么 \(fwt_A[i]\times fwt_B[i]=(\sum_{i\star j=0} A_j-\sum_{i\star j=1}A_j)\times (\sum_{i\star k=0} B_k-\sum_{i\star k=1}B_k)\)
\(=\sum_{i\star (j\space\text{xor}\space k)=0} A_jB_k-\sum_{i\star (j\space\text{xor}\space k)=1} A_jB_k=fwt_C[i]\)

所以,\(fwt=merge(fwt_0+fwt_1,fwt_0-fwt_1)\)
\(a=merge(\dfrac{a_0+a_1}{2},\dfrac{a_0-a_1}{2})\)

事实上,我们分治的过程中,也就是,枚举二进制位。
然后对于 \(fwt_0,fwt_1\) 都考虑新填这一位的答案,并合并 \(fwt_0,fwt_1\)

code
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int N=(1<<17)+10,mod=998244353;
int add(int x,int y) {
	x+=y; if(x>=mod) x-=mod;
	return x;
}
int n,a[N],b[N],A[N],B[N];
void in() {
	for(int i=0; i<n; i++) A[i]=a[i],B[i]=b[i];
}
void out() {
	for(int i=0; i<n; i++) printf("%d%c",A[i],i==n-1?'\n':' ');
}
void get() {
	for(int i=0; i<n; i++) A[i]=1ll*A[i]*B[i]%mod;
}
void OR(int *f,int x) {
	for(int len=2; len<=n; len<<=1) {
		for(int i=0; i<n; i+=len)
			for(int j=0; j<(len>>1); j++)
				f[i+j+(len>>1)]=add(f[i+j+(len>>1)],1ll*f[i+j]*x%mod);
	}
}
void AND(int *f,int x) {
	for(int len=2; len<=n; len<<=1) {
		for(int i=0; i<n; i+=len)
			for(int j=0; j<(len>>1); j++)
				f[i+j]=add(f[i+j],1ll*f[i+j+(len>>1)]*x%mod);
	}
}
void XOR(int *f,int x) {
	for(int len=2; len<=n; len<<=1) {
		for(int i=0; i<n; i+=len)
			for(int j=0; j<(len>>1); j++) {
				int tmp=f[i+j];
				f[i+j]=add(f[i+j],f[i+j+(len>>1)]);
				f[i+j+(len>>1)]=add(tmp,mod-f[i+j+(len>>1)]);
				f[i+j]=1ll*f[i+j]*x%mod;
				f[i+j+(len>>1)]=1ll*f[i+j+(len>>1)]*x%mod;
			}
	}
}
int main() {
	scanf("%d",&n); n=1<<n;
	for(int i=0; i<n; i++) scanf("%d",&a[i]);
	for(int i=0; i<n; i++) scanf("%d",&b[i]);
	in(),OR(A,1),OR(B,1),get(),OR(A,mod-1),out();
	in(),AND(A,1),AND(B,1),get(),AND(A,mod-1),out();
	in(),XOR(A,1),XOR(B,1),get(),XOR(A,mod-mod/2),out();
	return 0;
}

子集卷积

对于 fwt 的过程,只需要再加上一维 \(i\)\(A_i\) 序列存储所有 \(popcount\)\(i\)\(a_i\),其他为 \(0\)
如果我们 \(|x\cup y|=|x|+|y|\),那么 \(x,y\) 是符合条件的。
即对于 \(C_i\),由所有 \(A_j\)\(B_{i-j}\) 卷积再求和而来。

CF662C Binary Table

考虑枚举翻转了那些状态的行,每列分别计算可以做到 \(O(2^nm)\)
不难发现,我们可以把每种状态的列都等价,取出来计算出现次数。
对于列,考虑初始状态 \(s\),以及最后状态 \(s'\),那么贡献是 \(s'\) 中 01 出现少的有多少个。
\(s\space \text{xor}\space s'= 行的状态\),变成卷积,即求 \(C_{i}=\sum_{s\space \text{xor}\space s'=i}cnt_s\times val_{s'}\),再取最小值即可。

P3175 [HAOI2015] 按位或

考虑拆位,将手里的数转化为 \(n\) 个只有 \(0,1\) 的数。
考虑 \(\min-\max\) 容斥,\(E(\max(U))=\sum (-1)^{|T|-1}\min(T)\)
\(\min(T)\) 使用暴力公式 \(\sum_{t=0}^\infty t\times p(\min(T)=t)\),其中 \(p(t)\) 表示在 \(t\) 秒恰好完成的概率。
对于 \(\min(T)\),只要有一个数满足条件即可,即 \(T\) 集合里任何数被选都可以。
设选不到 \(T\) 集合中的数为 \(q\)\(q\) 为超集和。
\(\min(T)=(1-q)\times (1+2q+3q^2+...)\)
\(=(1-q)\times((1+q+q^2+q^3...)+(q+q^2+q^3+...)+(q^2+q^3+q^4...))\)
等比数列求和,由于公差相同,\(=(1-q)\times \dfrac{1}{1-q}(1+q+q^2+...)=\dfrac{1}{1-q}\)
子集和,套用 FWT 即可。

P4221 [WC2018] 州区划分

先考虑一个州合法的条件,即存在欧拉回路,那么所有点 \(deg\) 都为偶数。
我们可以预处理求出所有的合法集合。
之后考虑 \(dp\),设 \(dp_s\) 表示当前填到 \(s\) 的贡献的和。
\(dp_s=\sum_{t \subset s} dp_t\times (\frac{sum(s-t)}{sum(s)})^p\)
这个 \(dp\) 具有阶段性,也就是 \(dp\) 数组会更新,更新的又要继续更新后面的,不能一次处理出。
考虑像子集卷积那样,新加一维状态,从 \(popcount\) 小的做到大的。

CF1119H Triple

暴力的话,做 \(O(n)\) 次卷积,复杂度炸裂。
考虑巧妙的计算 \(fwt\) 数组,对于位置 \(q\)\(fwt_q=\)
所有 \((-1)^{popcount(s\&x)}\cdot x+(-1)^{popcount(s\&y)}\cdot y+(-1)^{popcount(s\&z)}\cdot z\) 的积。
总共只有 \(8\) 种情况,求出每种情况的方案数,再乘起来即可。问题是求每种情况的个数。
先简化问题,对于 \((a,b,c)\),令 \(b=a\otimes b,c=a\otimes c\),最后再把所有 \(a\) 异或回去即可。
现在只有四种情况,\(x+y+z,x+y-z,x-y+z,x-y-z\),设方案数为 \(c_1,c_2,c_3,c_4\)
我们考虑弄出 \(4\) 个方程把 \(c\) 解出来。
考虑把只把所有 \(y\),或只把所有 \(z\),或只把所有 \(y\otimes z\) 做一遍 fwt,
对于 \(q\) 这个点的各项系数,可以求出:
\(n=c_1+c_2+c_3+c_4\)
\(fwt(y)_q=c_1+c_2-c_3-c_4\)
\(fwt(z)_q=c_1-c_2+c_3-c_4\)
\(fwt(y\otimes z)_q=c_1-c_2-c_3+c_4\)
解这个方程,最后 ifwt 回去即可。

P5387 [Cnoi2019] 人形演舞

我们可以把题目拆成 \(|V|\) 个独立游戏。
其次,我们发现,若 \(x\) 最高位是 \(k\),那么 \(SG(x)=x-2^k+1\).
所以,题目变成了选 \(|V|\) 个数使得 \(SG\) 异或起来非零。
考虑异或卷积,我们就是卷 \(|V|\) 次,考虑把 \(fwt\) 数组翻 \(|V|\) 次幂即可。

P5406 [THUPC2019] 找树

考虑矩阵树定理,里面填一个集合幂级数 \(f(x)=\sum cnt_ix^i\),最后判断方案数是否为 \(0\)
矩阵树定理成立的原因:集合幂级数构成了环,加法就是对应相加,乘法就是卷积。
由于行列式是线性变换,我们先把矩阵每个元素做好 fwt,
由于每个位置都是独立的,对于 fwt 每个位置求行列式,最后再 ifwt 回去即可。

posted @ 2024-03-15 21:57  s1monG  阅读(13)  评论(0编辑  收藏  举报