简单子集运算

参考


对于集合 \(U = \{1,2,\cdots,n\}\), 有下标系统 \(A = \{X\mid X\subseteq U\}\),和定义在 \(A\) 上的函数:\(f:A\to \mathcal R\)\(\mathcal R\) 大概是某个很牛逼的代数系统,有交换律什么的,应该直接默认是实数域就行了)

本文讨论对于这个函数 \(f\) 的变换。

用状压模拟 \(f\) 的下标系统:

n = read();
for (int I = 0; I < (1 << n); ++ i) f[i] = read();


子集和

\[\hat f(I) = \sum_{T\subseteq I} f(T) \]

求这个 \(\hat f\)

从另一个角度看待 \(f\) 的状压表示, 二进制的每一位看成多维数组的一个维度的某个数,那么这个子集和就是个高维前缀和。

插入

高维前缀和的算法:

对于一个高维点 \((x_1,x_2,\cdots,x_n)\), 其高维前缀和是对 \((y_1\le x_1,y_2\le x_2,\cdots,y_n\le x_n)\) 这些点值的求和。

将这些点值按照最后一维分类,将整个 n 维超立方体分成了 \(|\{k\mid k\le 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_{I\subseteq 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)];

补集和

这个东西是啥啊,原文没说我不知道。

posted @ 2021-03-13 09:21  xwmwr  阅读(225)  评论(0编辑  收藏  举报