【参考】
对于集合 (U = {1,2,cdots,n}), 有下标系统 (A = {Xmid Xsubseteq U}),和定义在 (A) 上的函数:(f:A o mathcal R)。((mathcal R) 大概是某个很牛逼的代数系统,有交换律什么的,应该直接默认是实数域就行了)
本文讨论对于这个函数 (f) 的变换。
用状压模拟 (f) 的下标系统:
n = read();
for (int I = 0; I < (1 << n); ++ i) f[i] = read();
子集和
[hat f(I) = sum_{Tsubseteq I} f(T)
]
求这个 (hat f)。
从另一个角度看待 (f) 的状压表示, 二进制的每一位看成多维数组的一个维度的某个数,那么这个子集和就是个高维前缀和。
插入
高维前缀和的算法:
对于一个高维点 ((x_1,x_2,cdots,x_n)), 其高维前缀和是对 ((y_1le x_1,y_2le x_2,cdots,y_nle x_n)) 这些点值的求和。
将这些点值按照最后一维分类,将整个 n 维超立方体分成了 (|{kmid kle x_n}|) 个 n - 1 维的超立方体, 看上去就十分显然了。
代码
for (int i = 0; i < n; ++ i)
for (int s = 0; s < (1 << n); ++ s)
if ((s >> i) & 1) f[s] += f[s ^ (1 << i)];
子集和的逆变换
就是给你 (hat f), 求 (f)。
有了前面的铺垫,这个东西就很简单了,就是对每一维从大到小扫过去差分,不过在这里一个维只有两个点值所以看起来就很简洁。
代码
for (int i = 0; i < n; ++ i)
for (int s = 0; s < (1 << n); ++ i)
if ((s >> i) & 1) f[s] -= f[s ^ (1 << i)];
超集和
[f(I)zeta = sum_{Isubseteq T} f(T)
]
给 f 求 fζ。
就是把 1 看成 0, 0 看成 1 了而已, 在一般的有限高维下标系统里, 可以直接看成把偏序关系反过来做子集和。
代码
for (int i = 0; i < n; ++ i)
for (int s = 0; s < (1 << n); ++ s)
if (! ((s >> i) & 1)) f[s] += f[s ^ (1 << i)];
超集和逆变换
不多说了,代码
for (int i = 0; i < n; ++ i)
for (int s = 0; s < (1 << n); ++ s)
if (! ((s >> i) & 1)) f[s] -= f[s ^ (1 << i)];
补集和
这个东西是啥啊,原文没说我不知道。