列车旅行分组算法

时间:2018-04-04 22:08:10

标签: algorithm

  

想象一下,在你面前有一个完整的日历年。在某些日子里,您可以乘坐火车,甚至可能在一天内进行几次,每次旅行都可以到达不同的地点(I.E.您为每次旅行支付的机票金额可能会有所不同。)

     

所以你会得到这样的数据:

Date: 2018-01-01, Amount: $5
Date: 2018-01-01, Amount: $6
Date: 2018-01-04, Amount: $2
Date: 2018-01-06, Amount: $4
...
     

现在您必须将此数据分组到存储桶中。一个桶最多可连续31天(无间隙),不能与另一个桶重叠。

     

如果一个水桶的列车行程少于32次,那么它将是蓝色的。如果它有32次或更多的火车旅行,它将是红色的。存储桶还将根据票证成本的总和获得一个值。

     

在对所有行程进行分组后,蓝色桶会被抛出。所有红桶的价值总结起来,我们称之为奖品。

     

目标是获得奖金的最高价值。

这是我遇到的问题。我不能想到一个很好的算法来做到这一点。如果有人知道接近这个的好方法,我想听听。或者,如果你知道其他任何地方可以帮助设计这样的算法。

2 个答案:

答案 0 :(得分:2)

这可以通过动态编程来解决。

首先,按日期对记录进行排序,并按顺序考虑它们。 让day (1)day (2),...,day (n)成为购买门票的日子。 允许cost (1)cost (2),...,cost (n)为相应的机票费用。

如果我们只考虑第一个fun (k)记录,请k为最佳奖品。 我们的动态编程解决方案将计算fun (0)fun (1)fun (2),...,fun (n-1)fun (n),使用之前的值来计算下一个值。

<强>基 fun (0) = 0

<强>过渡: 如果我们只考虑第一个fun (k)记录,那么最佳解决方案k是什么? 有两种可能性:删除k - 条记录,然后解决方案与fun (k-1)相同,或者k - 条记录是存储桶的最后一条记录。 然后让我们考虑循环中以k -th记录结尾的所有可能的桶,如下所述。

查看记录kk-1k-2,......,直至第一条记录。 让当前索引为i。 如果ik的记录连续超过31天,请从循环中断开。 否则,如果记录数量k-i+1至少为32,我们可以解决子问题fun (i-1),然后将记录从i添加到k ,获得cost (i) + cost (i+1) + ... + cost (k)奖。 值fun (k)是这些可能性的最大值,同时还有可能删除k条记录。

答案:只是fun (n),我们考虑了所有记录。

在伪代码中:

fun[0] = 0
for k = 1, 2, ..., n:
    fun[k] = fun[k-1]
    cost_i_to_k = 0
    for i = k, k-1, ..., 1:
        if day[k] - day[i] > 31:
            break
        cost_i_to_k += cost[i]
        if k-i+1 >= 32:
            fun[k] = max (fun[k], fun[i-1] + cost_i_to_k)
return fun[n]

目前尚不清楚我们是否可以将一天的记录拆分到不同的桶中。 如果答案是否定的,我们将不得不通过不考虑在一天内记录之间开始或结束的桶来强制执行它。 从技术上讲,它可以通过几个if语句来完成。 另一种方法是考虑天数而不是记录:而不是具有daycost的票证,我们将使用几天。 每天将有cost,即当天的门票总费用,以及quantity,门票数量。

编辑:根据评论,我们确实无法拆分任何一天。 然后,在进行一些预处理以获取天数记录而不是票据记录后,我们可以按照伪代码进行如下操作:

fun[0] = 0
for k = 1, 2, ..., n:
    fun[k] = fun[k-1]
    cost_i_to_k = 0
    quantity_i_to_k = 0
    for i = k, k-1, ..., 1:
        if k-i+1 > 31:
            break
        cost_i_to_k += cost[i]
        quantity_i_to_k += quantity[i]
        if quantity_i_to_k >= 32:
            fun[k] = max (fun[k], fun[i-1] + cost_i_to_k)
return fun[n]

此处,ik是天数。 请注意,我们会考虑范围内的所有可能天数:如果某一天没有门票,我们只使用零作为其costquantity值。

<强> EDIT2: 以上允许我们计算最大总奖金,但是那些让我们在那里的水桶的实际配置呢? 一般方法将是回溯:在k位置,如果最佳方式是跳过fun (k),我们将想知道我们如何得到k-1,并转换到k。第_条记录,或ki-1的{​​{1}}等式i等式fun[k] = fun[i-1] + cost_i_to_k。 我们继续进行,直到i降为零。

两种常用的实现方法之一是存储par (k),一个“父”,以及fun (k),它编码我们获得最大值的确切程度。 比方说,如果par (k) = -1,最佳解决方案会跳过k个记录。 否则,我们会在i中存储最佳索引par (k),以便最佳解决方案将ik的所有记录都包含在内。

另一种方法是不再存储任何东西。 相反,我们运行一个稍微修改的代码来计算fun (k)。 但是,我们不是将事物分配给fun (k),而是将分配的正确部分与我们已经获得的最终值fun (k)进行比较。 一旦他们平等,我们就找到了正确的过渡。

在伪代码中,使用第二种方法,使用天而不是单独的记录:

k = n
while k > 0:
    k = prev (k)

function prev (k):
    if fun[k] == fun[k-1]:
         return k-1
    cost_i_to_k = 0
    quantity_i_to_k = 0
    for i = k, k-1, ..., 1:
        if k-i+1 > 31:
            break
        cost_i_to_k += cost[i]
        quantity_i_to_k += quantity[i]
        if quantity_i_to_k >= 32:
            if fun[k] == fun[i-1] + cost_i_to_k:
                writeln ("bucket from $ to $: cost $, quantity $",
                    i, k, cost_i_to_k, quantity_i_to_k)
                return i-1
    assert (false, "can't happen")

答案 1 :(得分:1)

简化挑战,但不要太多,以制作一个可以用手解决的可忽略的例子。

这有助于找到正确的问题。

例如,仅需10天,最大长度为3的桶:

symbolic representation of tickets per day

对于构建存储桶并对其进行着色,我们只需要票数,此处为0,1,2,3。

平均而言,我们每天需要多个桶,例如2-0-2是3天内的4张门票。或1-1-3,1-3,1-3-1,3-1-2,1-2。

但是我们只能选择2个红桶:2-0-2和(1-1-3或1-3-3或3-1-2)因为1-2到底只有3个门票,但是我们至少需要4个(比每个桶的最大日跨度多一张票)。

虽然3-1-2显然比1-1-3票更多票,但票数减少可能更高。

蓝色区域是不太有趣的区域,因为它不会通过票数计算自己。