【问题标题】:Tourism - Canadian Computing Contest, Optimization Issues旅游 - 加拿大计算机竞赛,优化问题
【发布时间】:2019-06-06 05:32:53
【问题描述】:

来自 CCC 2019 的问题 4

问题描述 您正计划参观 N 个旅游景点。景点从1到N编号, 必须按此顺序访问。一天最多可以逛K个景点,想规划行程 尽量减少天数。 在这些限制条件下,您希望找到一个在景点之间具有良好平衡的时间表 每天参观。准确地说,我们分配一个分数 ai 吸引我。给定一个时间表,每一天都是 给定的分数等于当天访问的所有景点的最高分。最后,分数 将每一天相加得出时间表的总分。最大可能的总数是多少 使用尽可能少的天数? 输入规范 第一行包含两个空格分隔的整数 N 和 K (1 ≤ K ≤ N ≤ 10^6 )。 下一行包含 N 个空格分隔的整数 ai (1 ≤ ai ≤ 10^9 )。 对于 15 个可用标记中的 3 个,2K ≥ N。 对于 15 个可用标记中的另外 3 个,K ≤ 100 和 N ≤ 10^5 . 输出规格 输出单个整数,即可能的最大总分。 样本输入 5 3 2 5 7 1 4 样本输入的输出 12 样本输入的输出说明 我们需要至少两天的时间来参观所有景点,因为我们无法参观所有景点 一天。 第一天参观前两个景点得5分,参观后三个景点得5分 第 2 天将给出 7 分,总分 12 分。 第一天参观三个景点,第二天参观两个景点,这是唯一的可能 在尽可能少的天数内访问,总分为 7 + 4 = 11。

我的方法 使用递归遍历最初的景点,然后在第二天发送部分,并在每个递归中找到最高分

def scores(things, att, days):
    minper = attractions(att,row1[1])

    if att <= 0:
        return 0, False
    if days > minday:
        return 0, True
    else:
        maxscore = 0
        for y in range(minper,row1[1]+1):
            score, more = scores(things[y:],att-y,days+ 1)
            if not(more):
                score += max(things[:y+1])
            if score >= maxscore:
                maxscore = score
        return maxscore, False

def attractions(attr,maxper):
    if (attr % maxper) == 0 and attr > 0:
        return maxper
    return (attr % maxper)

row1=(input()).split(" ")
places=(input()).split(" ")

for x in range(len(row1)):
    row1[x] = int(row1[x])

for x in range(len(places)):
    places[x] = int(places[x])

if (row1[0]/row1[1])%1 == 0:
    minday = int(row1[0]/row1[1])

else:
    minday =(row1[0]//row1[1]) + 1

if minday !=1:
    maxscore = 0
    for x in range(attractions(row1[0],row1[1]),row1[1]+1):
        score, more = scores(places[x:],row1[0]-x,1)
        if not(more):
            score += max(places[:x])
        if score >= maxscore:
            maxscore = score
    print(maxscore)
else:
    print(max(places))

问题的测试用例包括 N 为 1000000 且 K 为 999999 的数字,这意味着这 2 天(最少天数)每天可能的行程为 1,999999 2,999998 ... 999998,2 999999 ,1,这个方法耗时太长,还有另一种情况,比如N=51,K=10,所以可以是10,10,10,10,10,1或者10,1,10,10, 10,10 或者它也可以是 9,9,9,9,9,6 和更多,但它涵盖了加起来最少天数的所有数字排列。如何更好地优化这一点,或者有更好的方法在 1 秒内完成。

【问题讨论】:

  • 由于这是一个竞赛问题,所以我不会发布答案,但会给出提示:如果我们将函数S(i, a)定义为您在使用i天后可以达到的最高总分a 的景点,您如何以动态编程方式使用以前的值计算该值?

标签: python algorithm optimization


【解决方案1】:

这是我的建议(一种天真的方法):

  • 最短的天数 (D) 将是 (N-1)//K+1
  • 如果 N 是 D 的倍数,那么您只有一种方法可以安排 D 天内的假期。
  • 否则,从前 D-1 天开始游览 K 个景点,最后一天游览其余景点 (R) = N-K*(D-1)
  • 将其保存在景点数量的基础数组中,其中位置代表天数
  • 然后,您只需在前 D-1 天分配 K-R 景点,与最后一天交换多个景点。
  • 使用蛮力方法,您将从 1 循环到 K-R,以获得要交换的景点数量 (S)。
  • 在该循环中,您需要在前 D-1 天中扩展 S 天的组合,并可能在同一天重复。 (Python 的 itertools 模块有一个功能:combinations_with_replacement)
  • 对于这些组合中的每一个,从基本数组的副本中交换相应的景点计数,计算并跟踪该假期计划的最高评分总数。
  • 时间应为 D*[1 + Cr(D-1,2) + Cr(D-1,3) + ... Cr(D-1,S)],其中 Cr(n,r) 为n 中的 r 与重复的组合:(r+n-1)!/(r!(n-1)!)

特殊情况:2K > N(最少天数 = 2)

当只有两个假期时,算法可以大大简化,因为这两天有一个共同的重叠,在任何一天都可以看到景点。还有首日必看的领先景点和第二天必看的尾随景点。因此,我们正在寻找领先群体、尾随群体和普通群体之间的前 2 个吸引力。领先群和尾随群各自最多只能有一个“头号景点”,普通群最多可以有两个。

因此,前 2 个景点在三组之间只有 4 种分配方式:

[---lead---][---common---][---tail---]
    X             X                    
                  X X             
                  X            X
    X                          X

在 max(lead)、max(tail) 和 top2 of common 之间取前 2 位将为我们提供最佳可能组合,如此处所示,有 4 个模拟值。

    MAX         TOP 2         MAX         TOP 2       Trip
[---lead---][---common---][---tail---]    of groups   TOTAL
    10          20,30         40          30,40       70
    10          20,40         30          30,40       70
    10          30,40         20          30,40       70
    20          10,30         40          30,40       70
    20          10,40         30          30,40       70
    20          30,40         10          30,40       70
    30          10,20         40          30,40       70
    30          10,40         20          30,40       70
    30          20,40         10          30,40       70
    40          10,20         30          30,40       70
    40          10,30         20          30,40       70
    40          20,30         10          30,40       70
  • 确定最小访问次数 (MV) = N % K
  • 第一次和最后一次 MV 访问必须是第一天和第二天的一部分(分别)
  • 这些将被识别为领先访问 (LV) 和尾随访问 (TV)
  • 从 LV+1 ... N-TV (包括 TopCommon1、TopCommon2)中查找常见访问 (CV) 中的前两个分数
  • 为主要和后续访问(LV->LT、TopLead -> TopTail)找到最热门的景点
  • 从 TopLead、TopCommon1、TopCommon2、TopTail 中找出前 2 个
  • 将它们加在一起以获得旅行的总分

其他情况下更好的算法

为了改进上述蛮力,我们可以在一天的第一个景点计算每个景点的部分行程分数。这可以从最后一个到第一个逐步解决。那么第一个景点的行程分数就是答案。

  • 从最后一个景点开始,倒退到第一个(AN = 景点编号)
  • 确定还有多少景点(包括这个):(RA) = N - AN + 1
  • 确定 RA 景点的天数 (RD) = (RA-1)/K+1
  • 确定当天需要完成的最少访问次数 (MV) = RA - K*(RD-1)
  • 当天可能的访问范围从 MV 到 K。
  • 对于此范围内的每个访问计数 (VC),计算该范围内景点的最大访问分数 (MS)(当天...+VC)。
  • 下一个景点将是第二天的第一个景点
  • 由于我们已经计算了从该景点开始的访问总行程,我们只需使用该计算值并添加当天的最佳景点 (MS)

请注意,这可以通过对边缘情况执行更简单的计算来进一步优化,例如“最小访问”范围内的第一次和最后一次访问。我认为这仅在 K 非常大(但不大于 N/2)且 N mod K 接近 K 的情况下才有意义

【讨论】:

  • 所以,我尝试了一种使用每天景点数量组合排列的方法,我发现最难面对的问题是,当一天开始时,暴力破解需要太多时间增加。您的解决方案可以工作,但不能在需要运行的 1 秒内工作,无论如何这可以以非暴力方式完成吗?
  • 您是否尝试过将额外的天数分配为带有记忆的递归。对于给定的 S 和 X 天到 D-1 天的组合,可能有大量多余的额外天数分布。 Python 在 functools 模块中有一个名为 lru_cache 的漂亮装饰器,可以很好地为现有函数添加记忆。
猜你喜欢
  • 2011-08-21
  • 2011-06-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-31
  • 1970-01-01
  • 2021-01-19
相关资源
最近更新 更多