【问题标题】:Guards and demand警卫和需求
【发布时间】:2013-02-04 12:24:28
【问题描述】:

你有 N 个守卫排成一列,每个守卫都需要硬币。只有当他的要求少于您在到达他之前已完全支付的金额时,您才可以跳过支付警卫。找出你花费最少的硬币来越过所有守卫。

我认为这是一个 DP 问题,但无法提出公式。另一种方法是对答案进行二分搜索,但我如何验证是否有多个硬币是可能的答案?

【问题讨论】:

  • N有什么限制和币的需求?
  • @ArmenTsirunyan 我猜 n 只是守卫的数量,即“可变”,同样每个守卫的需求也是可变的
  • 你能跳过一个以上的守卫吗?前任。 5 2 3 7,花费 5,然后跳过 2 和 3 ?
  • 是的,但显然在这种情况下,您想支付 5 和 2,仅此而已。
  • 我们通过的守卫顺序是固定的,对吧?

标签: algorithm dynamic-programming


【解决方案1】:

这确实是一个动态规划问题。

考虑函数f(i, j),它是true(一个),如果有第一个i守卫的分配给你成本j。您可以将函数f(i, j) 排列在大小为n x S 的表中,其中S 是所有警卫需求的总和。

让我们将d_i 表示为守卫i 的需求。 如果您有f(i),您可以轻松计算列f(i+1),只需扫描f(i) 并在f(i + 1, j) 为真且j < d_if(i + 1, j) 如果j >= d_i 时指定f(i+1, j + d_i) 为一。

这在O(nS) 时间和O(S) 空间中运行(您每次只需要保留两列),这只是伪多项式(如果需求以某种方式有界并且不随n 增长,则类似于二次) .

降低 DP 问题复杂性的一个常用技巧是获取最优解值的上限B。这样,您可以修剪不必要的行,获得 O(nB) 的时间复杂度(好吧,即使 S 是一个上限,但非常幼稚)。

事实证明,在我们的例子中,B = 2M,其中M 是守卫的最大需求。 事实上,考虑函数best_assignment(i),它为您提供通过第一个i 守卫的最少硬币数量。 让j 成为需求M 的守护者。如果best_assignment(j - 1) > M,那么显然整个序列的最佳分配是为第一个j-1 守卫的最佳分配支付守卫并跳过其他守卫,否则上限由best_assignment(j - 1) + M < 2M 给出。 但是在第一种情况下best_assignment(j - 1) 可以是多少?不能超过2M。 这可以用反证法来证明。让我们假设best_assignment(j - 1) > 2M。在这个任务中,守卫j-1 有报酬吗?不,因为2M - d_{j-1} > d_{j-1},所以不需要付费。对于j-2j-3、...1,同样的论点成立,因此没有支付警戒,这是荒谬的,除非M = 0(一个非常幼稚的案例需要检查)。

由于上限被证明是2M,所以上面说明的带有n 列和2M 行的DP 解决了这个问题,时间复杂度O(nM) 和空间复杂度O(M)

【讨论】:

  • 优秀的答案。非常有趣且展示得很好。
  • 谢谢。这仍然是一个伪多项式解决方案,因此知道是否存在完全多项式解决方案会很有趣。我认为,最有可能的是,有一个识别“强制付款”(即那些你必须支付警卫的情况)并巧妙地处理它,但不幸的是我没有时间进一步思考这个问题。
  • 如果使用平衡树表示给定的f(i) 列,则复杂性应该稍微优化。树的叶子应该是花费s 的总和,这样f(i, s) = TRUE。这是我当时想到的唯一改进
  • 由于您只需要遍历可能分配的列表,因此列表更合适。这可能会降低算法的实际效率,但不幸的是不是渐近的。
  • @akappa 你不认为你会在51 , 50 , 52 中出错... acc 。对于您的解决方案,您会得到51 + 52,但正确的答案应该是51 + 50
【解决方案2】:
function crossCost(amtPaidAlready, curIdx, demands){
    //base case: we are at the end of the line
    if (curIdx >= demands.size()){
        return amtPaidAlready;
    }

    costIfWePay = crossCost(amtPaidAlready + demands[curIdx], curIdx+1, demands);
    //can we skip paying the guard?
    if (demands[curIdx] < amtPaidAlready){
        costIfWeDontPay = crossCost(amtPaidAlready, curIdx+1, demands);
        return min(costIfWePay, costIfWeDontPay);
    }
    //can't skip paying
    else{
        return costIfWePay;
    }
}

这在 O(2^N) 时间内运行,因为每次执行它可能会调用自己两次。它是memoization 的一个很好的候选对象,因为它是一个没有副作用的pure function

【讨论】:

  • +1 如果所有需求的总和很小,此解决方案可能非常有效。这就是为什么我问 OP 是否对输入有任何限制
【解决方案3】:

这是我的方法:

int guards[N];
int minSpent;

void func(int pos, int current_spent){
    if(pos > N)
        return;
    if(pos == N && current_spent < minSpent){
        minSpent = current_spent;
        return;
    }

    if(guards[pos] < current_spent)      // If current guard can be skipped
        func(pos+1,current_spent);       // just skip it to the next guard
    func(pos+1,current_spent+guards[pos]);   // In either cases try taking the current guard
}

这样使用:

minSpent = MAX_NUM; 
func(1,guards[0]);

这将尝试 O(2^N) 的所有可能性,希望对您有所帮助。

【讨论】:

  • 不,它不一样,它会尝试所有的可能性,你能举个反例吗,我试过了,效果很好:)
猜你喜欢
  • 2011-01-30
  • 2017-05-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-19
  • 2017-04-29
  • 1970-01-01
相关资源
最近更新 更多