对卷积的一些理解与 FWT

UPD. 密码已去除

\(\lor\) 符号表示按位或(AND),\(\land\) 符号表示按位与(OR),\(\oplus\) 符号表示按位异或(XOR),\(\lnot\) 表示按位取反(NOT).

话说序列到底用 \(\langle a\rangle\) 表示好还是 \(\{a\}\) 表示好啊(

卷积还有一个理解是 https://gauss0320.blog.luogu.org/zong-xian-xing-bian-huan-di-jiao-du-kan-dai-fwt(Luogu4717 题解)

卷积 (Convolution)

数列卷积

卷积的定义:

两个序列 \(A,B\) 的卷积是

\[C_n=\sum_{i=-\infty}^{+\infty}A_iB_{n-i} \]

简记为 \(C = A * B\) .

然而我们一般所说的序列下标都是不小于 \(0\) 的,于是

\[\boxed{C_n=\sum_{i=0}^nA_iB_{n-i}} \]

这就是卷积的定义了 .

当然还可以写成这个样子

\[C_n=\sum_{i+j=n}A_iB_j \]


当然我们也可以定义一些别的卷积,类似这样:

\[\boxed{C_n=\sum_{i\circ j=n}A_iB_j} \]

其中 \(\circ\) 是一个运算符 .

注意我们保留了乘积的形式 .

卷积定理

对于一个神奇的函数 傅里叶变换(Fourier Transform,FT),有

\[\boxed{\mathcal F(f*g)=\mathcal F(f)\cdot \mathcal F(g)} \]

这就是卷积定理,关于 FT 是啥就不说了,直接按 DFT 理解就好了 .

卷积定理对于其他的卷积也有时成立 .

卷积定理可以大大加速卷积的计算,十分重要 .

生成函数角度的理解

生成函数(Generating Function,GF)大家都知道吧,其实我们各种类型的生成函数都可以通过下面这种柿子统一:

\[F(z)=\sum_na_nk_n(z) \]

其中 \(k\) 被成为 核函数 .

例如 \(k_n(z)=z^n\) 时就是 OGF,\(k_n(z)=\dfrac{z^n}{n!}\) 时就是 EGF,\(k_n(z)=n^{-z}\) 时就是 DGF,\(k_n(X)=\Pr(X=n)\) 时就是 PGF,等等 .


GF 的乘积就可以看作卷积,序列和生成函数之间形成一个一一对应 .

例如 OGF 情况,我们有

\[\begin{aligned}F(z)G(z)&=\left(\sum_na_nz^n\right)\left(\sum_nb_nz^n\right)\\&=\sum_{n}\sum_{m}a_nb_mz^{n+m}\\&=\sum_{k}\sum_{m}a_{k-m}b_mz^k\end{aligned} \]

最后一步是枚举 \(k=n+m\) .

这样我们就将 OGF 的乘积化成 OGF 形式了,提取系数,有

\[[z^n]F(z)G(z)=\sum_{m}a_{n-m}b_m \]

这不就是卷积嘛!

类似的,EGF 的乘积称作二项卷积,DGF 的卷积称作 Dirichlet 卷积,等等 .

位运算卷积与 FWT

位运算卷积,即

\[C_n=\sum_{i\circ j=n}A_iB_j \]

其中 \(\circ\)\(\land,\lor\)\(\oplus\)(即位运算).

其实这个位运算是可以规约到集合的


同普通卷积一样,我们要寻找一个函数 \(\mathcal S\),使得其满足卷积定理

\[\mathcal S(f*g)=\mathcal S(f)\cdot \mathcal S(g) \]

本质上来讲,是 https://www.luogu.com.cn/blog/wangrx/solution-p-4717 .

按位或 (FWT - OR)

显然有 \(j\lor i = i\)\(k\lor i = i\)\(j\lor k\lor i = i\) .

\(\displaystyle \operatorname{FWT}(\{a\})=\sum_{j\lor i = i}a_j\) .

则若在按位或卷积意义下有 \(c=a*b\),则

\[\begin{aligned} \operatorname{FWT}(\{a\})_i\operatorname{FWT}(\{b\})_i&=\left(\sum_{j\lor i = i}a_j\right)\left(\sum_{j\lor i = i}b_j\right)\\&=\sum_{j\lor i = i}\sum_{k\lor i = i}a_jb_k\\&=\sum_{(j\lor k)\lor i = i}a_jb_k\\&=\operatorname{FWT}(\{c\})_i\end{aligned} \]

于是 \(\operatorname{FWT}\) 就是我们要找的 \(\mathcal S\)

现在考虑怎么求 \(\operatorname{FWT}\)\(\operatorname{FWT}^{-1}\) .

Part I. FWT\(\operatorname{FWT}\)

将原序列 \(\{a\}\) 按二进制最高位分类,令 \(\{a_0\}\) 表示 \(\{a\}\) 下标最高位为 \(0\) 的,\(\{a_1\}\) 表示 \(\{a\}\) 下标最高位为 \(1\) 的,则

\[F(\{a\})=\operatorname{merge}(F(\{a_0\}),F(\{a_0\})+F(\{a_1\})) \]

其中 \(F=\operatorname{FWT}\)\(\operatorname{merge}\) 为序列拼接,\(+\) 是对应位相加 .

于是可以分治,从而 FWT-OR 被 \(O(n\log n)\) 解决 .

Part II. IFWT\(\operatorname{FWT}^{-1}\)

依然将 \(\{a\}\) 按二进制最高位分类,分法相同,由 Part I 知:

\[F(\{a\})=\operatorname{merge}(F(\{a_0\}),F(\{a_0\})+F(\{a_1\})) \]

其中 \(F=\operatorname{FWT}\),于是:

\[\{a\}=\operatorname{merge}(\{a_0\},\{a_1\}-\{a_0\}) \]

也可以分治,从而 IFWT-OR 被 \(O(n\log n)\) 解决 .

按位与 (FWT - AND)

和 FWT-OR 类似,令 \(\displaystyle \operatorname{FWT}(\{a\})_i=\sum_{j\land i = i}a_j\) 即可 .

发现 \(\displaystyle \sum_{j\land i = i}a_j=\sum_{j\lor i = j}a_j\),所以 FWT-OR 和 FWT-AND 是本质相同的 .

按位异或 (FWT - XOR)

其实 FWT 是叫 Fast Walsh–Hadamard Transform,然而 Walsh–Hadamard 这种原本就只指 FWT - XOR 的 .

定义 \(x\otimes y=\operatorname{popcount}(x\land y)\bmod 2\),其中 \(\operatorname{popcount}(x)\) 表示 \(x\) 二进制中 \(1\) 的个数 .

引一个引理:

Lemma

\[\operatorname{popcount}(x\oplus y)\equiv \operatorname{popcount}(x)+\operatorname{popcount}(y)\pmod 2 \]

证明可以参考 CF1615D 题解,可以直接考虑真值表 .

于是我们就有

\[\begin{aligned}i\otimes(j\oplus k)&\equiv\operatorname{popcount}(i\land(j\oplus k))\\&=\operatorname{popcount}((i\oplus k)\land(j\oplus k))\\&\equiv\operatorname{popcount}(i\oplus k)+\operatorname{popcount}(j\oplus k)\\&\equiv (i\oplus k)\otimes(j\oplus k)&\pmod 2\end{aligned} \]

显而易见的,就有 \(i\otimes(j\oplus k)=(i\oplus k)\otimes(j\oplus k)\) .

有了这个性质就好做了,定义 \(\displaystyle \operatorname{FWT}(\{a\})_i=\left(\sum_{i\otimes j=0}a_j\right)-\left(\sum_{i\otimes j=1}a_j\right)\) 即可 .

\[\begin{aligned}\operatorname{FWT}(\{a\})_i\operatorname{FWT}(\{b\})_i&=\left(\left(\sum_{i\otimes j=0}a_j\right)-\left(\sum_{i\otimes j=1}a_j\right)\right)\left(\left(\sum_{i\otimes j=0}b_j\right)-\left(\sum_{i\otimes j=1}b_j\right)\right)\\&=\left(\sum_{i\otimes j=0}b_j\right)\left(\sum_{i\otimes k=0}b_k\right)-\left(\sum_{i\otimes j=0}b_j\right)\left(\sum_{i\otimes k=1}b_k\right)-\left(\sum_{i\otimes j=1}b_j\right)\left(\sum_{i\otimes k=0}b_k\right)+\left(\sum_{i\otimes j=1}b_j\right)\left(\sum_{i\otimes k=1}b_k\right)\\&=\left(\sum_{i\otimes(j\oplus k)=0}a_jb_k\right)-\left(\sum_{i\otimes(j\oplus k)=1}a_jb_k\right)\\&=\operatorname{FWT}(\{a\}*\{b\})_i\end{aligned} \]

于是就类似 FWT-OR 了,令 \(F=\operatorname{FWT}\),则

\[F(\{a\})=\operatorname{merge}(F(\{a_0\})+F(\{a_1\}),F(\{a_0\})-F(\{a_1\})) \]

\[\{a\}=\operatorname{merge}\left(\dfrac{\{a_0\}+\{a_1\}}2,\dfrac{\{a_0\}-\{a_1\}}2\right) \]

注意 IFWT-XOR 要乘 \(\dfrac 12\) .

于是分治即可,FWT / IFWT - XOR 被 \(O(n\log n)\) 解决 .

从而位运算卷积(OR, AND, XOR)被 \(O(n\log n)\) 解决 .

代码

用 lambda 表达式精简代码 .

不要管 namespace 名 .

// since C++14
using namespace std;
typedef pair<int, int> pii;
typedef long long ll;
const int N = (1 << 17) + 233, P = 998244353, INV2 = 499122177;
int n;
vector<ll> a, b;
namespace FWTbitwise 
{
#define args vi& A, int i, int j, int k, int mode
	typedef vector<ll> vi;
	auto OR  = [](args){(A[i+j+k] += A[i+j] * mode) %= P;};
	auto AND = [](args){(A[i+j] += A[i+j+k] * mode) %= P;};
	auto XOR = [](args)
		 {
		 	(A[i+j] += A[i+j+k]) %= P;
		 	A[i+j+k] = (A[i+j] - A[i+j+k] * 2 % P) % P;
		 	(A[i+j] *= mode) %= P; (A[i+j+k] *= mode) %= P;
		};
#undef args
	inline void FWT(vi& A, int mode, auto method)
	{
		int n = A.size();
		for (int st = 2, k = 1; st <= n; st <<= 1, k <<= 1)
			for (int i=0; i<n; i+=st)
				for (int j=0; j<k; j++) method(A, i, j, k, mode);
	}
	inline void conv(vi& A, const vi& B){for (int i=0; i<A.size(); i++) (A[i] *= B[i]) %= P;}
}
int main()
{
	using namespace FWTbitwise;
	scanf("%d", &n); n = 1 << n;
	for (int i=0, x; i<n; i++) scanf("%d", &x), a.emplace_back(x);
	for (int i=0, x; i<n; i++) scanf("%d", &x), b.emplace_back(x);
	vector<ll> tmpa(a), tmpb(b);
	a = tmpa; b = tmpb; FWT(a, 1, OR);  FWT(b, 1, OR);  conv(a, b); FWT(a, P-1, OR);
	for (int i=0; i<n; i++) printf("%lld ", (a[i] + P) % P); puts("");
	a = tmpa; b = tmpb; FWT(a, 1, AND); FWT(b, 1, AND); conv(a, b); FWT(a, P-1, AND);
	for (int i=0; i<n; i++) printf("%lld ", (a[i] + P) % P); puts("");
	a = tmpa; b = tmpb; FWT(a, 1, XOR); FWT(b, 1, XOR); conv(a, b); FWT(a, INV2, XOR);
	for (int i=0; i<n; i++) printf("%lld ", (a[i] + P) % P); puts("");
	return 0;
}
posted @ 2022-06-22 15:59  Jijidawang  阅读(50)  评论(0编辑  收藏  举报
😅​