好吧,看来我有一个合理的答案。首先让我们定义binom(n,k)
为我们可以设置位的方式的k
数量n
。这就是经典的帕斯卡三角形:
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
1 7 21 35 35 21 7 1
1 8 28 56 70 56 28 8 1
...
轻松计算和缓存。请注意,每行的总和是1<<lineNumber
。
接下来我们需要的是partial_sum
那个三角形的:
1 2
1 3 4
1 4 7 8
1 5 11 15 16
1 6 16 26 31 32
1 7 22 42 57 63 64
1 8 29 64 99 120 127 128
1 9 37 93 163 219 247 255 256
...
同样,可以通过将前一行中的两个值相加来创建此表,除了每行上的新条目现在1<<line
是1
.
让我们使用上面的这些表来构造f(x)
一个 8 位的数字(它可以简单地推广到任意数量的位)。f(0)
仍然必须为 0。查看第一个三角形中的第 8 行,我们看到接下来的 8 个条目是f(1)
to f(9)
,都设置了一个位。接下来的 28 个条目 (7+6+5+4+3+2+1) 都设置了 2 位,因此是 f(10) 到 f(37)。接下来的 56 个条目 f(38) 到 f(93) 有 3 位,并且有 70 个条目设置了 4 位。从对称性我们可以看到它们以 f(128) 为中心,特别是它们是 f(94) 到 f(163)。显然,唯一设置为 8 位的数字排在最后,如 f(255)。
因此,通过这些表,我们可以快速确定在 f(i) 中必须设置多少位。只需在表的最后一行进行二进制搜索。但这并不能准确回答设置了哪些位。为此,我们需要前面的行。
可以从上一行创建表中的每个值的原因很简单。binom(n,k) == binom(k, n-1) + binom(k-1, n-1)。有两种设置了 k 位的数字:以 a 开头的0...
数字和以 . 开头的数字1...
。在第一种情况下,下一位n-1
必须包含这些k
位,在第二种情况下,下一位n-1
必须仅包含k-1
位。特殊情况当然是0 out of n
和n out of n
。
同样的结构可以用来快速告诉我们f(16)
必须是什么。我们已经确定它必须包含 2 位设置,因为它属于范围f(10) - f(37)
。特别是,它是 6 号,设置了 2 位(像往常一样从 0 开始)。将其定义为范围内的偏移量很有用,因为我们将尝试将此范围的长度从 28 缩小到 1。
我们现在将该范围细分为 21 个以 0 开头的值和 7 个以 1 开头的值。由于 6 < 21,我们知道第一个数字是零。在剩下的 7 位中,还有 2 位需要设置,所以我们在三角形中向上移动一条线,看到 15 个值以两个 0 开头,6 个以 01 开头。由于 6 < 15,f(16) 以 00 开头. 再往上走,7 <= 10 所以它以 . 开头000
。但是 6 == 6,所以它不以0000
but开头0001
。此时我们更改范围的开始,所以新的偏移量变为0(6-6)
我们知道需要只关注0001
以f(16)...f(19)
. 知道范围是 应该很明显f(16)=00010001, f(17)=00010010, f(18)=00010100, f(19)=00011000
。
因此,为了计算每一位,我们在三角形中向上移动一行,比较我们的“余数”,根据比较可能会向左一列添加零或一。f(x)
这意味着isO(bits)
或的计算复杂度O(log N)
以及所需的存储空间是O(bits*bits)
。