【问题标题】:Weighted Activity Selection Problem with allowing shifting starting time允许移动开始时间的加权活动选择问题
【发布时间】:2020-03-07 05:01:56
【问题描述】:

我有一些带有权重的活动,我想通过最大化总权重来选择不重叠的活动。这是已知问题并且存在解决方案。

就我而言,我可以在一定程度上改变活动的开始时间,而持续时间保持不变。这会给我一些灵活性,我可能会提高我的利用率。

示例场景类似于以下,其中所有活动都应该在区间 (0-200) 内:

(start, end, profit)
a1: 10 12 120
a2: 10 13 100
a3: 14 18 150
a4: 14 20 100
a5: 120 125 100
a6: 120 140 150
a7: 126 130 100

如果不改变灵活性,我会选择 (a1, a3, a6) 就是这样。另一方面,对于任何 t 给出。在这种情况下,我可能会想出这个时间表,并且可以选择除 a7 之外的所有任务,因为 shift 无法避免冲突。

t: 5

a1: 8 10 120 (shifted -2 to left)
a2: 10 13 100
a3: 14 18 150
a4: 18 24 100 (shifted +4 to right)
a5: 115 120 100 (shifted -5 to left)
a6: 120 140 150

在我的问题中,就活动持续时间而言,我拥有的总时间非常长。虽然活动平均需要 10 秒,但我的总时间甚至是 10000 秒。然而,这并不意味着可以选择所有活动,因为转移灵活性不足以使某些活动不重叠。

在我的问题中,还有一些重叠的活动集群和非常大的空白空间,没有活动,还有另一个重叠的活动集群,即 a1、a2、a3 和 a4 可以说是 cluster1 a5、a6 和 a7 是 cluster2。每个集群可以通过将其中的一些向左和向右移动来及时扩展。通过这样做,我可以选择比原来的活动选择问题更多的活动。但是,我不知道如何决定将哪些任务向左或向右移动。

我的期望是找到一个接近最优的解决方案,其中总利润会以某种方式局部最优。我不需要全局最优值。此外,我没有关于集群利用率的任何标准,即我不能保证每个集群的最小活动数量等。实际上,这些集群是我直观描述的。没有定义集群。然而,在时域中,活动以某种方式被分离为集群。

活动开始和结束时间也是整数,因为我可以忽略分数。我会有大约 50 个活动,平均持续时间为 10 个。时间窗口是10000。

这个问题有什么可行的解决办法吗?

【问题讨论】:

  • 将“班次”编码为离散的附加选项是否可行?例如,给定 2 小时的灵活性,而不是从 6 开始到 7 结束的灵活任务,我们的选择列表现在将包括 (4,5), (5,6), (7,8), (8 ,9) (6,7)? (然后我们必须保证每个这样的集合中最多有一个被选中。看起来像约束满足。)
  • 就我而言,我的活动时间大约为 5 秒,而我的灵活性大约为 2 分钟。因此,每个包含的任务不可能重叠。在这种情况下,我面临另一个问题;如何从非重叠组中选择最多一项任务。这也是另一个问题
  • 不确定我是否理解。问题正文中的一个最小示例以及数据类型(正如您所建议的,我们正在查看的范围类型)可能会有所帮助。
  • @SamiŞimşekli 我对“在我的问题中,我为时域中的每个活动都有足够的空间”感到有些困惑。 - 你是说你可以保证如果你以最佳方式转移它们,就可以选择所有活动?您的示例设法选择了所有活动,因此不会混淆。
  • 另外,您对每个集群内的最大活动数有一些期望/保证吗?

标签: optimization dynamic-programming greedy


【解决方案1】:

您提到您可以将活动划分为不重叠的集群,即使其中的活动被转移到一定程度。这些集群中的每一个都可以独立考虑,并且为每个集群计算的最佳结果简单地总结为最终答案。因此,算法的第一步可以是试运行,在两个方向上扩展所有活动,找出哪些活动形成集群,并独立处理每个集群。在最坏的情况下,所有活动都可能形成一个集群。

根据剩余集群的最大大小,有几种方法。如果它低于 20(甚至 30,取决于您是否希望程序在几秒钟或几分钟内运行),您可以将搜索给定集群中的所有活动子集与贪婪方法结合起来。换句话说:如果您正在处理 N 个元素的子集,请尝试它的每个2^N 可能的子集(好吧,2^N-1 如果我们忘记了空子集),检查这个特定子集中的活动是否可以安排在不重叠的方式,并选择符合条件且总和最大的子集。

我们如何检查给定的活动子集是否可以以不重叠的方式安排?让我们按照结束的升序对它们进行排序,并从左到右处理它们。对于每一项活动,我们都会尽可能早地安排它,确保它不会与我们已经考虑过的活动相交。因此,集群中的第一个活动总是比原计划早开始时间t,第二个活动要么在第一个结束时开始,要么比原计划早t,以较大者为准,依此类推。如果在任何时候我们都无法以不与前一个活动重叠的方式安排下一个活动,那么就无法以不重叠的方式安排该子集中的活动。该算法采用O(NlogN) time,总体上每个集群在O(2^N * NlogN) 中处理。再次注意,此函数增长得非常快,因此如果您要处理足够大的集群,则此方法不适用。

===

另一种方法特定于您提供的附加限制。如果活动的开始和结束以及参数t都是以整数秒为单位测量的,而t大约是2分钟,那么每个集群的问题都设置在一个小的离散空间中。即使您可以将任务定位为从非整数秒值开始,但始终存在仅使用整数的最佳解决方案。 (为了证明这一点,考虑一个不使用整数的最佳解决方案 - 因为t 是整数,所以您始终可以将任务从最左边开始向左移动一点,以便它从整数值开始。)

知道开始时间和结束时间是离散的,您可以构建一个 DP 解决方案:按照活动结束*的升序处理活动,并记住您可以从前 1、2 中获得的最大可能权重总和, ...,如果给定活动在时间 x 结束,则每个 x 的 N 个活动从 activity_start - tactivity_start + t。如果我们将此记忆函数表示为f[activity][end_time],则递归关系为f[a][e] = weight[a] + max(f[i][j] over all i < a, j <= e - (end[a] - start[a]),大致翻译为“如果活动a在时间e结束,则前一个活动必须在@987654338开始时或之前结束@ - 所以让我们选择之前活动及其结束的最大总权重,并添加当前活动的权重”。

*再一次,我们可以证明至少有一个最优答案保留了这个顺序,即使可能有其他最优答案不具备这个属性

我们可以更进一步并消除对先前活动的迭代,而是将此信息编码为f。然后其定义将更改为“f[a][e] 是第一个a 活动的最大可能总权重,如果它们都没有在e 之后结束”,递归关系将变为f[a][e] = max(f[a-1][e], weight[a] + max(f[a-1][i] over i <= e - (end[a] - start[a])])),其计算复杂度为@ 987654344@,其中X 是放置任务开始/结束的离散空间的总跨度。

我假设您不仅需要计算最大可能权重,还需要计算获得该权重所需选择的活动,甚至可能需要开始每个活动的确切时间。幸运的是,我们可以从 f 的值中推导出所有这些,或者在计算 f 的同时计算它。后者更容易推理,所以我们引入第二个函数g[activity][end]g[activity][end] 返回一对 (last_activity, last_activity_end),本质上为我们指出了 f[activity][end] 中最佳权重使用的确切活动及其时间。

让我们通过您提供的示例来说明其工作原理:

(start, end, profit)
a1: 10 12 120
a2: 10 13 100
a3: 14 18 150
a4: 14 20 100
a5: 120 125 100
a6: 120 140 150
a7: 126 130 100
  1. 我们按活动结束时间排序,从而交换 a7 和 a6。
  2. 我们为第一个活动初始化fg 的值:

f[1][7] = 120, f[1][8] = 120, ..., f[1][17] = 120,这意味着第一个活动可以在 7 到 17 之间的任何时间结束,并且花费 120。f[1][i] 对于所有其他 i 应设置为 0。

g[1][7] = (1, 7), g[1][8] = (1, 8), ..., g[1][17] = (1, 17),这意味着包含在f[1][i] 值中的最后一个活动是a1,它以i 结束。 g[1][i] 对于 [7, 17] 之外的所有 i 未定义/无关。

  1. 这就是有趣的开始。对于每个i 使得a2 不能在时间i 结束,让我们分配f[2][i] = f[1][i], g[2][i] = g[1][i],这基本上意味着我们不会在这些答案中使用活动a2。对于所有其他i,即在[8..18] 区间内,我们有:

f[2][8] = max(f[1][8], 100 + max(f[1][0..5])) = f[1][8]

f[2][9] = max(f[1][9], 100 + max(f[1][0..6])) = f[1][9]

f[2][10] = max(f[1][10], 100 + max(f[1][0..7]))。这是第一次第二个子句不仅仅是简单的 100,如f[1][7]>0。事实上,它是100+f[1][7]=220,这意味着我们可以进行活动a2,以一种在时间10 结束的方式移动它,并得到220 的总权重。我们继续以这种方式为所有i <= 18 计算f[2][i]

g 的值是:g[2][8]=g[1][8]=(1, 8)g[2][9]=g[1][9]=(1, 9)g[2][10]=(2, 10),因为在这种情况下,最好采取活动 a2 并在时间 10 结束它。

我希望这种持续的模式是可见的 - 我们计算 fg 到最后的所有值,然后在所有可能的结束时间 e 中选择最大值 f[N][e]活动。借助辅助函数g,我们可以向后遍历这些值以找出确切的活动和时间。即,我们使用的最后一个活动及其时间在g[N][e]。我们称他们为AT。我们知道 A 开始于T-(end[A]-start[A])。那么,之前的活动一定是在那个时候或者之前结束了——所以让我们看一下g[A-1][T-(end[A]-start[A]),以此类推。

请注意,即使您不将任何内容划分为集群,这种方法也有效,但通过分区,可以安排任务的空间大小会减少,运行时也会减少。

您可能会注意到,这些解决方案都不是输入大小的多项式。我有一种感觉,您的问题没有一般多项式解决方案,但我无法通过将另一个 NP 完全问题简化为它来证明它。真的很想阅读减少/更好的通用解决方案!

【讨论】:

  • 我现在正在尝试理解算法。在这部分我不明白为什么我们在这里得到 f[1][0..5] 的最大值:“f[2][8] = max(f[1] [8], 100 + max(f[1][0..5])) = f[1][8]" 。我的意思是为什么从 0 到 5,在下一行为什么从 0 到 6 等等?
  • 还有一个问题,这 f[1][i] 是不是错字? “...被包含在 f[1][i] 值是 a1”。我想它会是 g[i][i]。对吗?
  • 对于第一个问题 - 我们正在解开公式f[a][e] = max(f[a-1][e], weight[a] + max(f[a-1][i] over i <= e - (end[a] - start[a])]))i 的范围随着e 而增加。本质上,我们是在说“如果我当前的活动在时间 e 结束,那么上一个活动必须在当前活动开始之前结束,即 e-(end[a]-start[a])”。
  • 对于第二个问题,我猜想混淆来自于fg 背后缺乏精确的数学符号。我的意思是f[1][i] 保留了一些最优活动集的权重总和,g[1][i] 对应于该集中的最新活动。
  • g[A-1][T-duration(A)] 应该始终通过构造来定义和相关,但实际上将其从“存储最后一个活动”简化为“存储所有活动”使其更容易理解。唯一的问题是您的算法变慢了,因为它必须在更新 g 时覆盖 O(N) 值而不是 1 值 - 所以如果您需要加快速度,您仍然可以恢复到递归定义。我很想知道这在实践中是否有帮助!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-07
  • 2017-06-14
  • 1970-01-01
  • 2015-02-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多