【问题标题】:Greedily assigning scores to maximize the final result贪婪分配分数以最大化最终结果
【发布时间】:2017-12-19 21:46:16
【问题描述】:

给你一个 T 天的时间线和 N 个分数的列表。您必须将每个分数分配给一天(从 1 到 T),以使分配的总分数最大化。

虽然有限制。每个分数只能分配给有限的天数 X,也可以分配给发生在特定数字 Y 上或之后的天数。

输入是给定的格式:

T

没有

分数 X Y(150 4 1 表示分数 150 最多可以分配到第 1 天或之后的 4 天)

例如:

T = 10             

N = 5

150 4 1

120 4 3

200 2 7

100 10 5

50 5 1

注意 = 2 分数可以具有相同的值。每天最多可以分配1分。

上述示例的最佳结果是:150 150 150 150 120 120 200 200 120 120。

我尝试了什么:

我根据分数对列表进行排序,并首先分配最高分数。

在上面的示例中,我将从 200 开始并将其分配给 7 天和 8 天。

同样,我会将下一个最高分 150 分配给 1、2、3 和 4 天。 等等……

但这需要 O(N * T) 时间。 N 用于迭代分数列表,T 用于检查和分配时间线上的分数(在最坏的情况下)。

目标是最大化并计算最终得分。

有没有更优雅的方法来做到这一点?就像甚至没有分配分数,从而取消了 O(N * T) 的 T 部分。

【问题讨论】:

  • 恕我直言,这太悲观了,因为大多数时候您只需要遍历 T 的一小部分。平均而言,您应该非常接近 O(T)
  • @MikeMB 你能解释一下原因吗? N 因子显然会因为对 N 个排序分数的迭代而出现
  • 您对最小化算法的实际运行时间感兴趣吗?在这种情况下,如果您显示实际代码会有所帮助。
  • 不,实际上我的算法会保证运行时间不好,所以这个问题的目的是找到另一种最佳方法来获得解决方案。由于我知道上述算法行不通,我仍在考虑各种方法,但没有编写任何方法
  • “不会工作” - 为什么? T 和 N 的上限是多少?您是否有指向原始问题/meaningufl 数据集的链接?我想测试一些想法。

标签: c++ c algorithm sorting


【解决方案1】:

我编写了一个非常简单的算法实现:

#include <vector>
#include <algorithm>
#include <array>

constexpr int T = 10;
struct Item {
    int score;
    int count;
    int min;
};
std::array<Item, 5> input={{
    {150, 4, 1},
    {120, 4, 3},
    {200, 2, 7},
    {100, 10, 5},
    {50, 5, 1}
}};
std::array<bool, T> days{};

int main() {
    // preprocess input
    std::sort(input.begin(), input.end(), [](auto l, auto r) {return l.score > r.score; });

    int totalScore = 0;
    int lastFreeDay = T - 1;

    [&] {
        for (auto spec : input) {
            // scan forward to find open spots for the scores
            for (int pos = spec.min-1; spec.count && pos < lastFreeDay; ++pos) {
                if (!days[pos]) {
                    days[pos] = true;
                    totalScore += spec.score;
                    spec.count--;
                }
            }
            // we weren't able to assign all scores of this entry,
            // so every day after spec.min has already a score assigned to it.
            // lets scan backward and see where the last free one is
            if (spec.count > 0) {
                lastFreeDay = spec.min;
                while (days[lastFreeDay]) {
                    if (--lastFreeDay == -1) {
                        return;
                    }
                }
            }
        }
    }();

    return totalScore;
}

我不确定确切的算法复杂度是多少,但你可以看到两件事:

  • 一开始,冲突很少,所以内部循环实际上并不依赖T,所以它的行为更像O(N*k)(其中k是你可以分配的平均次数一个特定的分数)。
  • 即使 N 变得非常大,也不是所有分数都可以实际处理,因为该算法可以提前终止并将最近的空闲日与可以分配分数的最早日期进行比较。

当然,您可以创建一个最坏情况输入,其中您有 T*(T+1)/2 次内部循环传递(对于 N == T 和 k = 1 和 min_i = 1)但我的直觉是平均而言它比 O(N*T) 好得多,或者至少有一个非常小的常数(实际上,排序可能是主导因素)。

长话短说:我非常有信心您的算法实际上适用于实践,并且可能会通过 Prune 建议的更智能的数据结构进一步改进。

【讨论】:

    【解决方案2】:

    如果您维护一个可用间隔表,我相信您可以将其保持为 O(N + T)。不要每次都遍历整个长度T;只需检查您的开放间隔列表并从包含输入行的Y 值的第一个可用间隔开始。这个“开放”列表中的间隔不会超过 N/2,并且散列或二分查找都可以控制复杂性。

    【讨论】:

    • 嗨 Prune,你能建议我如何处理 C++ 中的开放间隔。使用什么数据结构以及如何使用它们
    • @Ravikishan:您可以使用自平衡二叉树(例如 RB-tree)。将每个区间的起点存储在树中就足够了。我认为这使得复杂度为 O(T log T)(对分数进行排序是 O(T log T),所以你不会丢失任何东西)因为在大小为 M 的平衡搜索树中找到一个元素是 O(log M) .
    猜你喜欢
    • 2011-07-07
    • 1970-01-01
    • 1970-01-01
    • 2017-07-13
    • 1970-01-01
    • 2015-06-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多