📜  兔子屋 | Google Kickstart 2021 A 轮(1)

📅  最后修改于: 2023-12-03 15:36:45.584000             🧑  作者: Mango

兔子屋 | Google Kickstart 2021 A 轮

简介

「兔子屋」是 Google Kickstart 2021 年第一轮的 A 题,问题描述如下:

假设你有一些盒子要放到兔子屋里。 它是一个类似于标准 X 轴上的数轴的平坦表面,其中每个位置都有一个非负整数的高度。 您希望所有的盒子都放在同一高度。 您可以执行以下操作来达到这一目标:

  • 选择两个不同的盒子,并将其中一个移动到另一个盒子的位置。 该操作的时间成本等于此两个盒子之间的距离。
  • 选择一个盒子并将其放在任意高于或低于它所在位置的位置。 该操作的时间成本等于该盒子移动的垂直距离。

给定一个长度为 N 的非负整数序列 H,描述了平面表面不同位置处的高度,则问题是找到一种方法可以在最小时间内将所有盒子移动到同一高度,并确定达到该高度的总时间。

解析

该题目可以使用数学解法及贪心解法,其中数学解法可能过于复杂,下面为贪心解法。

对于解决该问题有以下几个步骤:

  1. 确认输送货箱的高度范围

    统计所有货箱的高度 $H_i$ 的平均值 $avg$,将这个值向下取整作为输送货箱的高度。因为最终所有的箱子都要放到同一高度,而放置在平坦表面上的货箱高度只能是整数。

    在将所有货箱放在输送货箱中运输前最好将货箱按其高度分类,即为每个高度建立一个簇,更好地统计它们的数量和区间。其中一个簇可以简单地表示为一个包含一个起始索引 $l$ 和结束索引 $r$ 的区间。

    h[3,4,5,5,5,6,8] => [[3,3], [4,4], [5,5], [6,6], [7,7]]
    
  2. 移动货箱使其高度与输送货箱相同

    相对于输送货箱的高度,货箱的高度可分为三种:低于、等于和高于。对于低于输送货箱的货箱,可以选择将其提升到输送货箱的高度,而对于高于输送货箱的货箱可以选择将其降到输送货箱的高度。必然存在这样一个时间点,此时所有高度高于输送货箱的货箱都会比所有低于输送货箱的货箱先达到输送货箱的高度,在这个时间点以前,仅需要将所有高于输送货箱的货箱下移,所有低于输送货箱的货箱上移即可使高度达到输送货箱的高度。

    因此,我们只需要将一个簇视为一个整体,将其所有货箱上移或下移至输送货箱的高度而不必考虑具体每个货箱的移动。假设一个簇 $[l,r]$ 包含 $i$ 件货物, $j$ 件货物高度等于输送货箱高度,则移动这个簇的代价可以表示为:

    $$ \sum_{k=l}^r |H_k - avg| = (i-j) \times (avg-low) + (j) \times (high-avg) $$

    其中 $low$ 为低于输送货箱高度的最大高度, $high$ 为高于输送货箱高度的最小高度。

  3. 贪心处理每个簇

    因为是以整个簇的方式计算代价的,因此贪心策略就是尽可能多的让货物在一个簇,而不是将它们散落到多个簇中。在分配簇时贪心地分配将选择最小或最大的簇,这可以防止任何后续移动往返的情况。

    最后,用以上算出来的代价的和就是最终的答案。

代码

下面为 Python3 实现:

import sys

def solve():
    n = int(input().strip())
    heights = list(map(int, input().strip().split()))

    l, r = float('inf'), -float('inf')
    total, cnt = 0, 0

    # 统计每个高度的数量
    cnts = [0] * 10001
    for h in heights:
        cnts[h] += 1

    # 统计所有货箱的高度平均值
    avg = sum(heights) // n

    # 建立簇
    clusters = []
    j = 0
    for i in range(n):
        if heights[i] == avg:
            cnt += 1
        else:
            l = min(l, heights[i])
            r = max(r, heights[i])
            if i == n-1 or heights[i+1] == avg:
                clusters.append((l, r, cnt))
                total += (cnt * (avg - l) + cnts[avg] * (r - avg))
                cnt = 0
                l, r = float('inf'), -float('inf')

    return total

# 将问题长度读入并循环处理每个测试用例
t = int(input().strip())
for i in range(1, t + 1):
    print(f"Case #{i}: {solve()}")

由于题目数据结构较为简单,因此实现上只需要注意下标溢出及类型转换等问题即可。