【问题标题】:(maybe NP-Hard) Find total number of subsets of a set such that every subset when multiplied with all its elements has value greater than X(也许是 NP-Hard)找到一个集合的子集的总数,使得每个子集在乘以它的所有元素时具有大于 X 的值
【发布时间】:2017-06-03 06:02:09
【问题描述】:

有一个数组arr,大小为n。那么有多少2^n 子集的总乘积大于一个数字,比如 X?

n 大约是 2^5,X 可以更大大约 2^60(C++ long 变量中可以容纳的任何东西)

我认为类似于子集总和的东西会起作用,但我现在不这么认为。


我从 Codeforces 的 this question from a past contest 想到了这个问题。虽然问题不需要我问什么,但我很好奇。

【问题讨论】:

  • 如果你取所有元素和 X 的对数,那么这就变成了找出有多少子集的总和超过某个目标的问题(因为产品的对数是对数的总和) .这与维基百科上的“子集和问题”不同。但这些答案看起来很有希望:(1)geeksforgeeks.org/…(2)math.stackexchange.com/questions/1248463/…
  • 哦...但是现在数字不是浮点数(由于日志)-我的意思是我可以在什么基础上构建动态编程状态? (是的,我也意识到子集和问题与我认为的不同)
  • 是的,但这可能不是世界末日。如果值都是整数,您是否有代码可以用来查找总和超过目标的子集的数量?
  • 交叉发布:stackoverflow.com/q/43970179/781723math.stackexchange.com/q/2281798/14578cs.stackexchange.com/q/75418/755。请do not post the same question on multiple sites。每个社区都应该诚实地回答问题,而不会浪费任何人的时间。
  • @MatthiasFripp,对于未来,如果您要推荐另一个网站,最好告诉发帖人不要在多个网站上交叉发帖。您可以建议他们将他们的问题标记为迁移,或者(如果有任何答案或有用的问题)建议他们在将问题发布到其他地方之前在此处删除他们的问题,或者只是避免提出建议。这将为所有人带来更好的体验。请参阅我之前的评论以及因此在多个网站上发生的重复/碎片化。

标签: c++ algorithm


【解决方案1】:

您的问题可以按原样通过动态编程来解决,即不使用日志。

当输入整数以二进制形式给出时(而不是unary), 您的问题是在非自适应 2 查询缩减下的 NP-hard ,因为:

number of subsets whose product equals X
等于;
积大于 X-1 的子集数

乘积大于 X 的子集数。

我没有立即看到任何显示实际 NP 难度的方式(即,在 1 次查询减少下)。

【讨论】:

  • 我不想/不需要得到 product = X 的子集数量,而不是。乘积大于 X 的子集。
  • 当整数为二进制时,这就是我展示的在非自适应 2 查询缩减下的 NP-hard。​ ​
  • 抱歉,您能解释一下“非自适应 2 查询缩减”是什么意思吗?谢谢
  • 意思是Turing reduction,其中​[只查询了2个]​和​those queries are presented at the same time。​ ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​
【解决方案2】:

(在这个答案的第一个版本中,我提出了一个不幸包含逻辑错误的算法;这是一个完全重新设计的答案版本。)

正如你所说,生成所有子集并检查它们的乘积是否大于 X 给出了 2N 的复杂度,这对于较大的 N 值是有问题的。为了获得更有效的方法,我们'必须找到一种方法来计算有效和无效子集的数量,而无需实际生成它们。

让我们一步一步来,使用这个例子:

7   2   1   6   3   9   7   5   3   1   (X=240)  

乘积大于X的子集的数量与输入中整数的顺序无关;所以我们可以先从大到小对输入进行排序,而不改变结果。

9   7   7   6   5   3   3   2   1   1   (X=240)  

向子集添加 1 不会改变其乘积。因此,如果列表末尾有一个或多个 1,我们可以将它们删除,并将缩减列表的结果乘以 2M(其中 M 是 1 的数量)。我们现在有一个整数列表,其中 N=8 和最小值为 2。

9   7   7   6   5   3   3   2   (X=240, M=2)  

如果我们从末尾开始迭代列表,并将每个整数添加到一个子集中,直到其乘积大于 X,我们就知道随机子集始终有效的最小大小 P(因为我们使用的是最小的整数)。

9   7   7   6   5   3   3   2
                            2 =   2
                        3 x 2 =   6
                    3 x 3 x 2 =  18
                5 x 3 x 3 x 2 =  90
            6 x 5 x 3 x 3 x 2 = 540 > X  ->  P = 5

如果我们向这个子集添加整数,或者用更大的整数替换整数,子集仍然有一个大于 X 的乘积。这意味着所有具有至少 P 个整数的子集都是有效的子集。有 (N 选择 P) + (N 选择 P+1) + ... + (N 选择 N) 这些;在我们的示例中,N = 8 和 P = 5:

 subsets with size 5 = (8 choose 5) = 56  
 subsets with size 6 = (8 choose 6) = 28  
 subsets with size 7 = (8 choose 7) =  8  
 subsets with size 8 = (8 choose 8) =  1  
                                      --
                                      93 subsets

(请注意,P 受目标 X 的位大小限制。如果 X 是 64 位无符号整数,则任何 64 个整数的子集将始终具有大于 X 的乘积。)

如果我们从头开始迭代列表,并将每个整数添加到一个子集,而它的乘积小于 X,我们就知道随机子集不能对其有效的最大大小 Q(因为我们使用的是最大的整数)。

9   7   7   6   5   3   3   2
9                             =   9
9 x 7                         =  63      ->  Q = 2
9 x 7 x 7                     = 441 > X

如果我们从这个子集中删除整数,或者用更小的整数替换整数,这个子集仍然有一个小于 X 的乘积。这意味着所有最多有 Q 个整数的子集都不是有效的子集。

所以现在我们知道大小为 1 和 2 的子集都是无效的,大小为 5 到 8 的 93 个子集都是有效的,我们必须检查大小为 3 和 4 的子集;我们将按尺寸检查它们,先是 3,然后是 4。

为了避免生成和检查所有大小为 3 的子集,我们会将选择分成最后具有一定数量 0(未选择的整数)的组,并计算可能的最低(和最高)乘积,例如对于带有三个尾随 0 的组:

*   *   *   *   1   0   0   0   <- all possibilities
0   0   1   1   1   0   0   0   <- smallest possible product
1   1   0   0   1   0   0   0   <- greatest possible product

在给出这些最小和最大产品的示例中:

9   7   7   6   5   3   3   2   (X=240, SIZE=3)  

*   *   1   0   0   0   0   0   = 441 ~ 441 > X   (2 choose 2) =  1
*   *   *   1   0   0   0   0   = 294 ~ 378 > X   (3 choose 2) =  3
*   *   *   *   1   0   0   0   = 210 ~ 315  
*   *   *   *   *   1   0   0   =  90 ~ 189 < X
*   *   *   *   *   *   1   0   =  45 ~ 189 < X
*   *   *   *   *   *   *   1   =  18 ~ 126 < X

我们发现前两组的乘积总是大于 X(我们只需要计算最小乘积即可知道这一点),因此我们计算它们的数量并将其添加到总数中。对于最小值小于 X 的组,我们也计算最大值,我们发现最后三个组总是有一个小于 X 的乘积,所以我们可以忽略它们。

(一旦找到最大值低于 X 的组,您可以跳过检查下一组,因为它们的最大值永远不会更大。)

剩下的就是后面三个 0 的组。要检查该组中有多少子集是有效的,我们选择最小整数 (5),减小子集大小,然后递归:

9   7   7   6   (product * 5 > X=240, SIZE=2)  

*   1   0   0   = 63 ~ 63 * 5 > X    (1 choose 1) = 1
*   *   1   0   = 49 ~ 63 * 5 > X    (2 choose 1) = 2
*   *   *   1   = 42 ~ 54 * 5

我们发现前两组总是有效的,但是第三组的乘积可以小于或大于X。所以我们选择最小的整数(6),递减子集大小,递归:

9   7   7   (product * 5 * 6 > X=240, Size=1)

1   0   0   = 9 * 30 > X    (0 choose 0) = 1  
*   1   0   = 7 * 30 < X  
*   *   1   = 7 * 30 < X  

我们发现只有第一个组总是有效的,所以我们计算它的数量(这是最低的递归级别,所以这是 1)并将其添加到总数中。所以我们发现大小为 3 的有效子集的数量是 8。然后我们继续对大小为 4 的子集做同样的事情:

9   7   7   6   5   3   3   2   (X=240, SIZE=4)  

*   *   *   1   0   0   0   0   = 2646 ~ 2646 > X    (3 choose 3) =  1
*   *   *   *   1   0   0   0   = 1470 ~ 2205 > X    (4 choose 3) =  4
*   *   *   *   *   1   0   0   =  630 ~ 1323 > X    (5 choose 3) = 10
*   *   *   *   *   *   1   0   =  270 ~ 1323 > X    (6 choose 3) = 20
*   *   *   *   *   *   *   1   =   90 ~  882

我们对最后一组进行递归:

9   7   7   6   5   3   3   (product * 2 > X=240, SIZE=3)  

*   *   1   0   0   0   0   = 441 ~ 441 * 2 > X    (2 choose 2) = 1
*   *   *   1   0   0   0   = 294 ~ 378 * 2 > X    (3 choose 2) = 3
*   *   *   *   1   0   0   = 210 ~ 315 * 2 > X    (4 choose 2) = 6
*   *   *   *   *   1   0   =  90 ~ 189 * 2 
*   *   *   *   *   *   1   =  45 ~ 189 * 2 

我们递归倒数第二组:

9   7   7   6   5   (product * 2 * 3 > X=240, SIZE=2)  

*   1   0   0   0   = 63 ~ 63 * 6 > X    (1 choose 1) = 1
*   *   1   0   0   = 49 ~ 63 * 6 > X    (2 choose 1) = 2
*   *   *   1   0   = 42 ~ 54 * 6 > X    (3 choose 1) = 3
*   *   *   *   1   = 30 ~ 45 * 6

我们对最后一组进行递归:

9   7   7   6   (product * 2 * 3 * 5 > X=240, SIZE=1)  

1   0   0   0   = 9 * 30 > X    (0 choose 0) = 1
*   1   0   0   = 7 * 30 < X
*   *   1   0   = 7 * 30 < X
*   *   *   1   = 6 * 30 < X

我们向后退一步并递归到最后一组:

9   7   7   6   5   3   (product * 2 * 3 > X=240, SIZE=2)  

*   1   0   0   0   0   = 63 ~ 63 * 6 > X    (1 choose 1) = 1
*   *   1   0   0   0   = 49 ~ 63 * 6 > X    (2 choose 1) = 2
*   *   *   1   0   0   = 42 ~ 54 * 6 > X    (3 choose 1) = 3
*   *   *   *   1   0   = 30 ~ 45 * 6
*   *   *   *   *   1   = 15 ~ 27 * 6 < X

我们递归倒数第二组:

9   7   7   6   (product * 2 * 3 * 5 > X=240, SIZE=1)  

1   0   0   0   0   = 9 * 30 > X    (0 choose 0) = 1
*   1   0   0   0   = 7 * 30 < X
*   *   1   0   0   = 7 * 30 < X
*   *   *   1   0   = 6 * 30 < X

所以对于大小为 4 的子集,我们总共找到 59 个有效子集;这给出了:

 subsets with size 5 ~ 8 =  93
 subsets with size 4     =  59 
 subsets with size 3     =   8
                           ---
                           160

因为我们在开始时去掉了两个 1,所以我们必须将其与 22 相乘,总共是:

160 * 2^2 = 640

总共 2^10 = 1024 个子集中的有效子集。为了得到这个结果,我们计算了大约 50 个子集的乘积。

从示例中可以看出,有一些子集产品的计算在多次递归中重复。这意味着动态编程技术(例如记忆化)可以大大加快算法速度。


复杂性和最坏的情况

输入的排序需要 O(N.LogN),然后是几个步骤,每个步骤都需要 O(N)。

对于最后一步,最坏情况的输入当然是您可以创建一个包含 1 个整数的有效子集和一个包含 N-1 个整数的无效子集,例如如果列表包含大量小整数和比目标 X 大一个大整数;这意味着您必须详细检查所有子集大小。

最后一步很难量化;最坏的情况至少是 O(N2),但随后递归深度开始发挥作用。我猜一个包含很多重复值的列表会产生很多重叠范围和大量递归,以及 O(N3) 的复杂性。

将最后一步视为最复杂的步骤似乎很明显,因此将整体复杂度置于 O(N3),但因为大小为 64 或更大的子集始终有效(对于X)的64位值,最后一步的复杂度可以增长多少是有限制的,对于较大的N值,算法开头的排序可能会成为主导因素。

【讨论】:

  • 你能解释一下你的更新吗? n 也小于或等于 32,所以没关系
  • @four_lines 我已经用另一个排序方向修改了答案,这更有意义。
  • 哦,这很有道理,很好!我想知道最坏的情况复杂性是什么......我认为这可能是针对{10^18,2,2,...2}的一些集合
  • @four_lines 我刚刚意识到该算法包含一个致命缺陷;我已经更新了问题以解释错误。您可能不应该接受答案;你总是可以给我一个努力的支持:-)
  • 如果你到达例如subsetProduct(000100011) &gt;= X,你可以说从最左边的 4 个元素中选择一个元素,然后从剩下的 8 个元素中选择 2 个元素将导致总和 >= X。然后你需要计算有多少 subsetProduct(0000????? with 3 1's) &gt;= X。然后对 2 个 1 和 1 个 1 做同样的事情。这个想法可以用来“修复”这个算法,但这可能会使其成指数增长。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-26
相关资源
最近更新 更多