如果该集合S = {1,...,N}
被划分为两个总和相等的子集,则该总和必须是 的总和的一半S
;的总和S
是N(N+1)/2
所以分区中每个子集的总和必须是N(N+1)/4
。它也必须是整数,因此N(N+1)/2
必须是偶数。
所以问题归结为找到总和为 的 S 的子集的数量N(N+1)/4
。分区的总数正好是这个数字的一半,因为每个分区包含两个这样的子集,并且没有两个分区共享一个子集。
这应该是显而易见的。
现在,让我们列出任何和任何集合的S
总和的子集。任何这样的子集都必须有一个最大值,它必须是 的一个元素。最大值必须是 的最大元素,或者必须小于 的最大元素。这两组子集是不同的,所以我们可以分别列举出来。我们称 为 的最大元素。k
k
S
S
S
S
S
Smax
第二组很简单:它只是总和为的子集。我们可以通过递归调用子集列表器来找到它们。但是第一组几乎一样简单:组中的每个集合都包括,其其余元素是一些集合,其中加起来为,我们可以再次递归列出。为了完成递归,我们注意到 if是空集,那么 if ,恰好有一个合格子集(空集本身),如果 k 不为 0,则不存在合格子集。每次递归都会从 中删除一个元素,因此最终必须达到终止条件。S - { Smax }
k
Smax
S - { Smax }
k - Smax
S
k = 0
S
应该清楚,S
上述递归函数将使用的子集只是从1
to的数字,因此我们可以完全摆脱,并将递归编写如下:Smax
S
Subsets(min, max, k) =
Subsets(min, max - 1, k)
⋃ { {max, P} | P ∈ Subsets(min, max - 1, k - max) }
但是我们只想要分区数的计数,所以我们可以稍微简化一下:
Count_Subsets(min, max, k) =
Count_Subsets(min, max - 1, k)
+ Count_Subsets(min, max - 1, k - max)
我们需要通过添加结束条件来完成递归:
If min > max, Count_Subsets(min, max, k) = 1 if k = 0; otherwise 0
(事实上,很容易证明递归意味着1
当 k 递减到0
并且0
如果k
小于时该值将是0
,因此我们可以使终止条件更早发生。)
That gives us the simple recursion for the count. But since it calls itself twice, it would be better to work backwards ("dynamic programming"). We need to compute Count_Subsets(1, N, N*(N+1)/4)
, which will require us to compute values of Count_Subsets(1, max, k)
for all values of max from 1 to N, and from all values of k from 0 to N*(N+1)/4. We do that by starting with max = 0, and working up until we reach min = N. And that's precisely what your algorithm does; i
is max
, and the array is the set of values for k from 0 to N(N+1)/4
.
By the way, as should be apparent from the above description, a[j]
should be incremented by a[j - i]
, not by a[j - 1]