【问题标题】:Finding the sequence so that the event is finished at the earliest查找顺序以便事件最早完成
【发布时间】:2017-10-04 23:21:36
【问题描述】:

这是来自 informatica olympiad 的一个问题,我一直试图解决这个问题。这对我来说很重要,因为这包含一个我在很多问题中看到的潜在基本问题。

给 N 个公民参加一个活动,他们必须在一台计算机上编程,吃巧克力,然后吃甜甜圈。 time ,第 i 个公民为每个任务所花费的时间作为输入。每个公民必须按顺序完成任务,即先编程,然后吃巧克力,然后吃甜甜圈。任何数量的人一次都可以吃巧克力或甜甜圈,但由于计算机是一台,每次只有一个人可以编程。一次,他完成了,他会转向巧克力,下一个人将编程。任务是找出公民被派往编程的顺序,以使事件在最短的时间内结束,这个时间是输出。

我使用以下方法解决了这个问题: 如果我从第 i 个公民开始,那么对于剩下的 n-1 个公民,如果我找到时间 (tn-1) 则 tn = max((ni[0]+ni[1]+ni[2]), ni[0] + TN-1)。例如。: 18 7 6 23 10 27 20 9 14

然后 18+7+6, 18+23+10+27, 18+23+20+9+14, max 将是 84,但如果你从 23 开始,那么时间将是 74,这是更少的。

我实现了这种方法,我在这里展示它的代码。然而,对于我的方法来说,复杂度是 O(n!)。我可以看到潜在的重复子问题,所以我可以使用 DP 方法。但问题是我需要存储每个列表 i 到 j 的时间值,以便它可以以从 i 到 j 的任何 k 开头,依此类推。这个存储过程又会很复杂并且需要 n!贮存。如何解决这个问题和类似的问题?

这是我的方案:

#include <iostream>
#include <vector>
#include <climits>

int min_time_sequence(std::vector<std::vector<int> > Info, int N)
{
    if (N == 0) return 0;
    if (N == 1)
    {
        int val = Info[0][0] + Info[0][1] + Info[0][2];
        return val;
    }
    std::vector<std::vector<int> > tmp = Info;
    int mn = INT_MAX;
    for (int i = 0; i < N; ++i)
    {
        //prepare new list
        tmp.erase(tmp.begin()+i);
        int mn = min_time_sequence(tmp, N-1);
        int v1 = Info[i][0] + mn;
        int v2 = Info[i][0] + Info[i][1] + Info[i][2];
        int larger = v1 > v2 ? v1 : v2;
        if (mn > larger) mn = larger;
    }
    return mn;
}

int main()
{
    int N;
    std::cin>>N;
    std::vector<std::vector<int> > Info;
    //input
    for (int i = 0; i < N; ++i)
    {
        std::cin>>Info[i][0];
        std::cin>>Info[i][1];
        std::cin>>Info[i][2];
    }
    int mx = 0;
    if (N > 0)
        mx = min_time_sequence(Info, N);
    std::cout<<mx<<std::endl;
    return 0;
}

【问题讨论】:

  • 这是一个加权间隔调度问题,增加了一些额外的复杂性以确定作业是否兼容。你可以在 O(nlogn) 时间内完成一些工作,一旦你知道要搜索什么,网络上就有很多资源。
  • 这不是加权区间调度问题,我也想不出怎么近似。在这里,没有开始时间和结束时间,任何公民都可以开始任何时间,那么如何调和呢?如果你想通了,你能提供一种方法吗?
  • 这里是一个类似问题的链接,但我看不出所提出的方法是如何工作的? stackoverflow.com/questions/28648370/…

标签: c++ algorithm dynamic-programming


【解决方案1】:

由于您询问了通用技术,您可能想查看贪婪算法,即反复优化下一个选择的算法。在这种情况下,可能是剩下的总时间最长(3次之和)的人下一个程序,所以他或她会更快地吃完饭,而晚开始的人不会花更多的时间。

如果这样的算法是最优的,程序可以简单地按时间总和以降序对列表进行排序,这需要 O(N log N) 时间。

但是,您需要证明您的解决方案是有效的。一种方法被称为“贪婪保持领先”。这是一个归纳证明,你可以证明你的贪心算法产生的解决方案在第一步至少是最优的(在某种程度上相当于最后一步的最优),然后它在第二步也一样好,之后的步骤,依此类推。提示:您可以尝试衡量每个人开始编程后事件可能需要多长时间的最坏情况。在最后一步,当最后一个人开始编程时,这相当于最优。

另一种证明算法最优的方法是“通过交换证明”。这是一种矛盾的证明形式,您假设某些不同的解决方案是最佳的,然后您表明将该解决方案的一部分与您的解决方案的一部分交换可以改进所谓的最佳解决方案。这与它曾经是最优的假设相矛盾——这证明没有其他解决方案比这更好。所以:假设最佳顺序不同,这意味着最后一个完成的人在其他花费更少时间的人之后开始。如果这两个人的位置互换会怎样?

贪婪的解决方案并不总是最好的,因此如果它们不是最好的,您可能需要考虑其他技术,例如对称破坏和尽早修剪搜索树。

【讨论】:

  • 不,贪婪在这里行不通。考虑以下值:18 7 6 23 10 27 20 9 14 从 23 开始,然后 20 将是​​解决方案,尽管 23 大于 18(解决方案还取决于吃巧克力和甜甜圈的值)
  • @RohitRoy 也许再看看算法的作用?它根据每个人的 3 次总和进行排序,而不是单独编程时间。在您的示例 IIUC 中,共有三个人的总时间为 18+7+6=31、23+10+27=60 和 20+9+14=43。所以贪心算法说从人 2(​​谁在 23 中编程)开始,然后是人 3(谁在 20 中编程),这是正确的(您可以验证这需要 66 时间,这是最优的)。
  • 在第一段中添加了一个说明,因为我可以看到这种误读是如何发生的。
  • 感谢您的澄清。我试过了,但没有得到正确的结果。我尝试的方法是按降序对所有 3 个值的总和进行排序(如果总和的 2 个值相同,则编程时间较短的值优先)。然后我计算了排序后的序列中的最大时间,但没有得到正确的结果。这是我的代码:codechef.com/viewsolution/13481927
  • 不让我看到。我可能犯了一个错误,但你能给我一个反例吗?
猜你喜欢
  • 1970-01-01
  • 2014-12-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-25
  • 2021-03-23
  • 2012-10-26
  • 1970-01-01
相关资源
最近更新 更多