【问题标题】:Explanation for appropriate partition-counting algorithm?适当的分区计数算法的解释?
【发布时间】:2013-01-08 19:09:10
【问题描述】:

我一直在解决一些算法编程问题,并且有一个问题。问题与本题中引用的问题相同:USACO: Subsets (Inefficient)

我能够编写一些对于高 N 来说太慢的(非动态)解决方案。不得不作弊并在网上查找一些解决方案。事实证明,快速算法非常简单,但即使知道答案,我仍然看不出如何从问题中得到答案。我可以看到等和子集形成的模式,但我看不到这些模式与算法解决方案之间的联系。

问题(上面的链接)是:

给定一组从 1 到 N (1

对于较大的集合,答案要么是 0(当 N*(N+1)/2 为奇数时),要么由以下简单算法给出:

  arr = array of int with (N*(N+1)/4)+1 elements
  arr[0]=1  // all other elements initialized to 0
  for i = 1 to N
    for j = N*(N+1) / 4 downto i
      add arr[j-i] to arr[j]
  subsetpaircount = arr[N*(N+1)/4] / 2

再次,我可以看到算法是如何工作的,我什至插入了打印语句,这样我就可以“观察”它是如何工作的。我只是看不出算法的操作如何与生成两组分区的不同方式的模式联系起来。

链接问题中的回答可能是相关的,但我也无法连接它是如何工作的:“这与在多项式 (x^1+1/x) 中找到系数 x^0 项相同)(x^2+1/x^2)...(x^n+1/x^n)..."

谁能为我澄清一下联系,或者指出一些解释这个特定问题的参考资料?谢谢。

【问题讨论】:

  • 'increment arr[j] by arr[j-1]' 是什么意思?我认为增量是一个数字加 1 的东西。此外,算法中是否存在 arr[i] 以值 0 开头的假设?如果不是,那么数组单元的起始值是多少?
  • @Lee,抱歉,我的伪代码约定可能并不完美。我经常在 Pascal 中编程,其中函数 inc(arg1,arg2) 会将 arg1 增加 arg2 的数量。 arr[i] 确实从元素零开始,我省略了初始化它的值的关键步骤,我刚刚添加了它。 . .
  • @Lee:增量的使用是正确的。它means“增加或增加,尤其是固定规模的系列之一:“工资可以以 5 美分的增量升级。”

标签: algorithm math combinatorics partition-problem


【解决方案1】:

我认为您的伪代码中可能有一个错误导致了混乱。我希望这条线

add arr[j-1] to arr[j]

成为

add arr[j-i] to arr[j]

假设是这种情况,那么考虑这个问题的方法是,在 i 上循环的每次迭代开始时,数组 arr[j] 包含选择整数子集 1 的方式的数量,2,...,i-1 使得所选整数的和正好是 j。

开始时,i=1,因此子集的唯一选择是总和等于 1 的空子集。

这就是为什么 arr[0]=1(表示使用空子集得到总和为 0)而所有其他条目为 0(因为没有办法得到非零和)。

从那时起,每次迭代都考虑将数字 i 添加到子集。得到和 j 的方法的数量取决于是否包含 i。

如果不包括 i,那么我们有与以前相同数量的方式(即 arr[j])。

如果包含 i,那么为了得到包含 i 在内的 j 的总和,我们必须将 i 添加到 1,..,i-1 的所有子集,这些子集的总和为 j-i。按照设计,如果我们查看索引 j-i,我们的数组正好包含这个数字。

所以得到j和的方法总数变成arr[j]+arr[j-i]。

当 i 循环完成时,arr 会为您提供选择子集并获得所需总和的方法的数量。我们知道 1,2,..,n 的总和是 n*(n-1)/2,所以如果我们计算有多少子集达到该值的一半,那么我们就是将分区计算为相等的总和。

实际上,这多算了 2 倍,因为它将 {1,2}/{3] 和 {3}/{1,2} 视为单独的解决方案,因此最终答案除以 2。

【讨论】:

  • 谢谢!这对于 i 的递增以及根据设计如何在 i 是当前集合的一部分的情况下数组已经有要添加的子集计数特别有用。
【解决方案2】:

如果集合S = {1,...,N}被划分为两个相等和的子集,那么这个和必须是S之和的一半; S 的总和是 N(N+1)/2,因此分区中每个子集的总和必须是 N(N+1)/4。它也必须是整数,因此N(N+1)/2 必须是偶数。

所以问题归结为找到总和为N(N+1)/4 的 S 子集的数量。分区的总数正好是这个数字的一​​半,因为每个分区包含两个这样的子集,并且没有两个分区共享一个子集。

这应该是显而易见的。

现在,让我们列出对于任何 k 和任何集合 S 的总和为 kS 的子集。任何这样的子集都必须有一个最大值,它必须是S 的一个元素。最大值必须是S 的最大元素,或者它必须小于S 的最大元素。这两组子集是不同的,所以我们可以分别列举出来。我们把S的最大元素称为S<sub>max</sub>

第二组很简单:它只是S - { S<sub>max</sub> } 的子集,它们总和为k。我们可以通过递归调用子集列表器来找到它们。但是第一组几乎一样简单:组中的每个集合都包含S<sub>max</sub>,其余元素是S - { S<sub>max</sub> } 中的一些集合,它们加起来为k - S<sub>max</sub>,我们可以再次递归列出。为了完成递归,我们注意到如果S 是空集,那么如果k = 0 恰好有一个合格子集(空集本身),如果k 不为0,则没有合格子集。每次递归都会从S 中删除一个元素,因此最终必须达到终止条件。

应该清楚,上述递归函数将使用的S的子集只是从1S<sub>max</sub>的数字,所以我们可以完全去掉S,写成递归如下:

Subsets(min, max, k) =
  Subsets(min, max - 1, k)
  &Union; { {max, P} | P ∈ Subsets(min, max - 1, k - max) }

但我们只想要分区的数量,所以我们可以简化一下:

Count_Subsets(min, max, k) =
  Count_Subsets(min, max - 1, k)
  &plus; Count_Subsets(min, max - 1, k - max)

我们需要通过添加结束条件来完成递归:

If min &gt; max, Count_Subsets(min, max, k) = 1 if k = 0; otherwise 0

(其实很简单,递归意味着当k递减到0时,值将是1,如果k小于0,则0,所以我们可以使终止条件发生得更早。)

这为我们提供了计数的简单递归。但是由于它调用了自己两次,所以最好向后工作(“动态编程”)。我们需要计算 Count_Subsets(1, N, N*(N+1)/4),这将要求我们计算从 1 到 N 的所有 max 值以及从 0 到 N*(N+1)/4 的所有 k 值的 Count_Subsets(1, max, k) 值。我们从 max = 0 开始,直到达到 min = N。这正是您的算法所做的; imax,数组是从 0 到 N(N+1)/4 的 k 值的集合。

顺便说一下,从上面的描述中可以看出,a[j] 应该增加a[j - i],而不是a[j - 1]

【讨论】:

  • 很好的解释,谢谢!我已经得到了明显的部分,但我不会在没有帮助的情况下摸索剩下的部分,即使看到了最终的算法并且知道答案是由更小的部分组成的。或者,也许我只是不够专注或不够坚持。 . .
  • 另外,关于一些可能详细涵盖此类问题的介绍性文本有什么建议吗?我有 Cormen,其中有关于分区和子集总和问题的部分,但对我来说这太粗略了,尤其是。当我不完全理解这个问题和经典的“子集和”问题时。我不需要被灌输,但更详细一点的东西让我自己拼凑起来会很有帮助。
  • @HerbertSitz,它只是应用最简单形式的动态规划来简化递归。 Cormen 有一整章是关于 DP 的,但无论出于何种原因,它都低估了 DP 基本上只是递归反转这一事实,Sedgwick 至少在他更简短的大纲中提到了这一点(我是 Sedgwick 的忠实粉丝,fwiw。) DP 通常用于优化问题,但它可以用于有效地计算许多递归问题。值得将其添加到您的心理工具包中。
  • @HerbertSitz:解决分支递归(否则将具有指数计算时间)的另一种方法是记忆或缓存结果。这可能比 DP 更精确,因为它只计算您需要的值。另一方面,记忆的开销可能比计算一些额外值的开销要高得多。练习:对于分区问题,写一个memoization和一个DP解,比较解的子问题个数。你能描述这两种方法的复杂性吗?理论上哪个更好?在实践中?
  • @HerbertSitz,最后:DP 主要用于优化问题的原因是,如果您不知道从哪里开始,递归就没有用处。 DP只要求你知道在哪里结束,你可以按照正确的顺序探索回到终点线的路径。将此与深度优先搜索进行对比。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-22
  • 1970-01-01
  • 2020-12-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多