2n
的二进制数,其中n
可能约为1000
。我们正在寻找具有以下特性的第k
个数字(k
受到10^9
的限制):
1
的数量等于0
的数量,可以描述为:#(1) = #(0)
- 这个数字的每个前缀都必须包含至少与
1
一样多的0
。在否定这个句子后可能更容易理解:没有前缀会包含比0
更多的1
。
基本上就是这样了。
为了让它更清晰,让我们举个例子:
n=2
,k=2
我们必须取长度为2n
的二进制数:
0000
0001
0010
0011
0100
0101
0110
0111
1000
and so on...
现在我们需要找到满足这两个条件的第二个数字。所以我们看到0011是第一个,0101是第二个。
如果我们改变k=3,那么答案不存在,因为有一些数字具有相同数量的相反位,但对于0110,存在前缀011,因此数字不满足第二个约束条件,所有具有1作为最高有效位的数字都是如此。
那么我到目前为止是如何找到算法的呢?
我的第一个想法是生成所有可能的位设置,并检查它是否具有这两个属性,但生成所有可能性将需要O(2^(2n)),这对于n=1000不是一个选项。
此外,我意识到没有必要检查所有比
0011
小的数字,对于n=2
,000111
对于n=3
等等......坦率地说,那些最高有效位的一半保持“未触及”的数字因为这些数字没有可能满足#(1) = #(0)
条件。利用这一点可以将n
减少一半,但是并没有太大的帮助。我仍然有一个永远运行的算法,而不是2倍的永远。它仍然具有O(2^n)
复杂度,这太大了。
有任何算法的想法吗?
结论
这篇文章是在阅读安迪·琼斯(Andy Jones)的帖子后思考后产生的结果。
首先,我不会发布我使用的代码,因为这是来自安迪帖子Kasa 2009中的第6点。你所要做的就是将nr
视为我描述的k
。非排名Dyck单词算法将帮助我们更快地找到答案。但它有一个瓶颈。
while (k >= C(n-i,j))
考虑到
n <= 1000
,卡特兰数可能非常巨大,甚至达到 C(999,999)
。我们可以使用一些大数算术,但另一方面,我想出了一个小技巧来绕过它并使用标准整数。只要卡特兰数比
k
大,我们就不需要知道它实际上有多大。因此,现在我们将创建卡特兰数,并在 n x n
表中缓存部分和。... ...
5 | 42 ...
4 | 14 42 ...
3 | 5 14 28 ...
2 | 2 5 9 14 ...
1 | 1 2 3 4 5 ...
0 | 1 1 1 1 1 1 ...
---------------------------------- ...
0 1 2 3 4 5 ...
生成它非常简单:
C(x,0) = 1
C(x,y) = C(x,y-1) + C(x-1,y) where y > 0 && y < x
C(x,y) = C(x,y-1) where x == y
所以我们只能看到这个:
C(x,y) = C(x,y-1) + C(x-1,y) where y > 0 && y < x
可能会导致溢出。
让我们在这一点上停下来并提供定义。
k-flow
- 它不是整数的真正溢出,而是值C(x,y)
大于k
的信息。
我的想法是在运行上述公式后检查每个C(x,y)
是否大于k
或任何总和组件是否为-1
。如果是,则用-1
替换它,这将作为标记,表示已发生k-flow
。我认为很明显,如果k-flow
数字与任何正数相加,它仍将被k-flowed
,特别是2个k-flowed
数字的总和是k-flowed
。
最后我们要证明的是没有创造真正溢出的可能性。真正的溢出只可能发生在我们求和a + b
时,它们都不是k-flowed
,但作为总和,它们产生了真正的溢出。
a + b <= 2 * k <= 2*10^9 <= 2,147,483,647
,其中不等式中的最后一个值是带符号int类型的值。我还假设int类型有32位,就像在我的情况下一样。
000111
,那么第二个数字将是000111 + 111
,即01110
,对于k=4,你将得到00001111
,然后加上1111
,所以你得到了第二个匹配的数字,即00011110
,依此类推。如果我错了,请纠正我。 - mamdouh alramadan00011110
包含前缀0001111
,不符合第二个属性。 - abc