从一次选择说起
记
从 中选择 个元素时,如果只关心选中了哪些元素,而不关心选取顺序,那么结果就是 的一个 元子集。Colex 编码可以把所有这样的子集连续地编号为
现在把问题向前推进一步。我们不只选择一次,而是分成 轮:第 轮选择 个元素,已经选过的元素不能再次选择。每轮内部仍然不计顺序,但轮与轮之间不能交换。
例如,从 中第一轮选择两个元素、第二轮选择一个元素时,
是一份选择结果。符号 只负责保留轮次边界,所以它与
不是同一个结果。
本文先重新理解 colex 为什么能给一次选择编号,然后在它的基础上回答:怎样给这样的多轮选择连续编号,并且从编号恢复原来的各轮集合。
Index set 与递减序列
长度为 的 index set 是 的一个 元子集;全体这样的集合记为 。
每个 都可以唯一地按严格递减的顺序写成
反过来,每个满足上述条件的序列也唯一确定一个 index set
因此, 与所有长度为 的严格递减非负整数序列之间存在一个自然的双射。下文将通过这个双射把二者视为同一个对象:讨论集合运算时使用集合的观点,书写编码时则使用递减序列的观点。
Colex 序
两个集合 的对称差 ,是所有只属于其中一个集合的元素组成的集合。对于两个不同的 index set,定义
也就是说,从最大的整数向下检查两个集合,第一个只属于其中一个集合的整数在哪个集合中,哪个集合就在 colex 序中靠后。这等价于从最大下标开始,按字典序比较两个集合的 0–1 特征序列,因此给出了一个全序。
Colex 编码就是前驱的数量
定义 colex 编码
这个公式最直接的解释是: 恰好等于 在 colex 序中的前驱数量。
记 为 的前驱集合。按照对称差最大元素的不同,将它划分为
这个分组条件可以画成下面的形式:
图中,浅绿色表示与 一致的固定前缀,浅蓝色表示可变部分。若 ,那么 在所有大于 的整数上都与 一致,并且不包含 。因此,它具有唯一的递减表示
反过来,每个满足上述条件的递减序列都唯一确定一个 。所以 与从
中选取 个元素的方法一一对应,于是
这些集合彼此不交,并且恰好覆盖 。由加法原理,
因此,colex 编码不是一个偶然的组合数公式:它就是 index set 在 colex 序中的秩。
Colex 给出的序同构
对于固定的 ,每个 index set 都只有有限多个前驱。另一方面,固定其余各项并让 增大, 中的
趋于无穷,所以前驱数量没有上界。
现在任取 ,选一个至少有 个前驱的 。有限初始区间
中的第 个元素恰好有 个前驱,因而编码为 。这说明 不会跳过任何非负整数。
此外,若 ,那么 的所有前驱以及 本身都是 的前驱,所以
因此编码保持 colex 序,并且不会重复。换言之,
是一个序同构。
限制到 的 元子集时,这些集合构成 colex 序的一个有限初始区间。它们一共有 个,所以
也是一个序同构。这里
从一次选择到多轮选择
现在固定 和 个非负整数
记所有合法的多轮选择为
这行记号只是把开头的问题重新写了一遍:第 轮选择 个元素,不同轮选到的元素不能重复。
要数清这些结果,最自然的方向是沿着轮次向前:先选择 ;确定 后再选择 ;确定 后再选择 ;依此继续。
第一轮有
种选择。选完第一轮后,无论 具体包含哪些元素,都恰好剩下 个元素,所以第二轮总有
种选择。继续下去,第 轮总有
种选择。
这里最重要的是量词的顺序:对于每一个已经确定的前缀,下一轮的具体候选内容可能不同,但候选数量总是同一个 。
所以多轮选择的总数是
不过,知道总数还不等于已经找到编码。下一步要做的是:把每个前缀下内容不同的候选列表,都统一编号为 。
把剩下的元素重新编号
假设前几轮已经使用了集合 。把 从 中删掉,再把剩余元素从 开始依次编号。
例如取
删除 后,剩余列表为
重新编号就是
一般地,剩余元素 的新编号为
公式右边减去的是“排在 前面、但已经被删掉的元素数量”。因此, 就是 在剩余列表中的 0-based 位置。
这个操作是一个保序双射
它不会丢失信息:给定新编号,只要在剩余列表里取相同位置,就能恢复原来的元素。
若下一轮要选择两个元素,上例中的所有选择会被压缩成:
所以,先压缩再做 colex,可以把这一轮的三个选择依次标为 。反向读取也同样直接:先由 colex rank 恢复压缩集合,再到剩余列表中按位置取出实际元素。
回到一般的第 轮。记前 轮已经使用的元素为
将 中的每个元素按 压缩,得到 元子集
再对 做 colex,得到这一轮的局部编号
注意, 的具体数值确实依赖前面的选择,因为压缩时使用了 。不依赖前缀具体内容的是 的范围:每个前缀下,它都恰好遍历 。
为什么局部编号构成笛卡尔积
先看最小的两轮例子:
第一轮有 个选择。选完第一轮以后只剩两个元素,所以每个第一轮结果都有 个后继。所有情况可以展开成下面的表:
例如,局部编号 的读取过程是:
- 在第一轮选择 ;
- 此时剩余列表是 ;
- 选择剩余列表中位置为 的元素,所以 。
因此 唯一确定 。反过来,对 逐轮压缩,也会重新得到 。
一般情况下,可以把全部选择过程看成一棵深度为 的树。第 层的一个节点,就是一个已经确定的前缀
每个这样的节点都有 个后继,而且这些后继都被可逆地标成
所以,从根走到一个叶子时,沿途记录的编号
恰好落在
中。
这个对应是双射。正向编码时,逐轮压缩并记录 ;反向恢复时,从第一轮开始,依次对 做 colex unranking,再从当前剩余列表中取回实际元素。每一步都可逆,所以整条路径也可逆。
Mixed-radix 序
现在暂时忘掉集合,只考虑有限数字向量
其中的元素记为
把 视为最低位,把 视为最高位。对两个不同向量 ,令
定义
也就是说,从第 位向第 位检查,最高的不同位决定大小。
定义第 位的权重
以及 mixed-radix 编码
Mixed-radix 编码也是前驱的数量
记 为 在 下的前驱集合。按照最高不同位 ,将它划分为
高于 的位置必须与 相同;第 位可以取 ,共有 种选择;所有低位完全自由,共有
种选择。因此
每个前驱都有唯一的最高不同位,所以这些集合彼此不交,并且恰好覆盖 。于是
mixed-radix 编码同样不是偶然的位权公式:它就是数字向量在 中的前驱数量。
一共有
个元素,因此
是一个序同构。逆映射就是逐位取余:令 ,依次计算
把各轮编号拼起来
回到多轮选择。先逐轮压缩并做 colex,得到
再做 mixed-radix,最终编号就是
因为“多轮选择 局部编号向量”和“局部编号向量 整数”都是双射,最终编码也是双射。它的值域恰好是
这个乘积也可以写成
最后看一个完整例子。取
第一轮的 colex rank 为
删除 后,剩余列表是 ,所以实际元素 被压缩为位置 。因此
最终编号为
反向恢复时,先由
得到 ;再逐轮做 colex unranking,并在每一步把压缩坐标放回当前剩余列表,便恢复 。
最终结论
从一次选择走到多轮选择,整个编码可以分成三步:
- colex 给每一轮的有限子集编号;
- 删除已使用元素并重新编号,使不同前缀下的候选列表都变成同一个连续区间;
- mixed-radix 把各轮的局部编号拼成一个整数。
Colex 与 mixed-radix 看起来是两套不同公式,但它们有同一个解释:编码值就是对象在所选顺序中的前驱数量。正因为如此,最终编号自然从 开始,没有空缺,没有重复,并且能够反向恢复原来的多轮选择。