【问题标题】:How to implement solution for Race simulation problems如何实现竞赛模拟问题的解决方案
【发布时间】:2015-05-13 17:16:04
【问题描述】:

面试时问了一个问题:

在一级方程式挑战赛中,有 n 支队伍,编号从 1 到 n。每个团队都有一辆汽车和一名司机。汽车规格如下:

  • 最高时速:(150 + 10 * i) 公里/小时
  • 加速度:(2 * i) 米每秒平方。
  • 处理系数 (hf) = 0.8
  • Nitro:将速度提高到两倍或最高速度,以较低者为准。只能使用一次。

这里 i 是团队编号。 赛车排队参加比赛。第 (i + 1) 辆汽车的起点线在第 i 辆汽车后面 200 * i 米处。

他们都同时开始,并试图达到他们的最高速度。每 2 秒重新评估一次位置(因此,即使汽车在其间越过终点线,您也会在 2 秒后知道)。在这个评估过程中,每个司机检查他的车10米范围内是否有车,他的速度降低到:hf *(当时的速度)。此外,如果车手注意到他是比赛中的最后一名,他会使用“nitro”。

以队伍数量和赛道长度为输入,计算最终速度和对应的完成时间。

我不明白如何处理这类问题。对于每个实例,我应该检查每对驱动程序的所有C(n,2) 组合并计算结果吗?但是我怎么知道我应该在什么情况下进行计算呢?

【问题讨论】:

  • 我不确定,但如果我有你的问题,我认为你必须在“客户端-服务器”模型中实现。你有一个负责保存比赛的服务器。客户端是汽车(团队)。在每一步,客户都会告诉服务器他们的信息,服务器存储它。他们可以访问所有其他汽车信息。所以在每一步他们都会收到完整的汽车列表和他们的信息。所以他们可以找到如果 10 米内有汽车,时间为 O(n)。就像真正的一级方程式赛车一样,显示所有车手的排名和他们在屏幕上的位置!

标签: algorithm design-patterns data-structures


【解决方案1】:

如果您查看Conway's Game of Life,您会发现与种族问题有很多共同点。

这是类比:

  • 初始状态(系统的种子):
    • 生命游戏:网格上的初始模式。每个单元格具有以下参数:
      • x 和 y 坐标
      • 细胞是活的还是死的
    • 比赛问题:n 辆车,每辆车都有预定的参数和轨道长度 l。每辆车都有以下参数:
      • 最高速度
      • 加速
      • 处理因素
      • 在轨道上的位置
      • 当前速度
  • 这些规则应用在离散时刻,称为滴答声。
    • 生命游戏:规则同时应用于上一代的每个细胞,产生下一代。每一代都是前一代的纯函数。
    • 种族问题:规则同时应用于前一个状态的每辆车,产生下一个状态。这每 2 秒发生一次。与 Game of Life 相同,每一步都是前一步的纯函数,这意味着它仅取决于前一状态的汽车参数。

不同的是,生命游戏永远不会结束,而比赛问题应该在每辆车的当前位置大于或等于轨道长度 l 时终止(尽管最后一个陈述是有争议的:由于处理因素,它是可能的在某些情况下,有些车永远不会到达终点线)。

关键是计算是在离散时刻完成的,这回答了您的问题:

但是我怎样才能确定我应该在什么情况下进行计算呢?

您可以从Algorithms 部分获取想法来解决此问题。您需要有 2 个汽车数组:一个代表当前状态,另一个代表下一步。在每次迭代中,您按照分配的规则重新计算每辆车的当前位置和速度,并检查循环是否应该终止。在下一次迭代之前,您交换数组角色,以便上一次迭代中的后继数组成为下一次迭代中的当前数组。

高级伪代码可能如下所示:

n = ..; // initial number of cars
l = ..; // track length
Car[] currentState = initializeState(n, l);
Car[] nextState = clone(currentState);
for (int iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
    calculateNextState(currentState, nextState, iteration);
    swap(currentState, nextState);
    if (shouldTerminate(currentState, l) {
        break;
    }
}

printResultOrClaimNotTerminated(currentState);

规则应用在 calculateNextState(..) 函数中。在最幼稚的实现中,您检查每对给您的汽车

O (C(n, 2)) = O (n * (n - 1) / 2) = O (n ^ 2)

每次迭代的复杂性。但是,您可以在此处考虑可能的优化。例如,您可以先按当前位置对汽车进行排序 (O (n * log(n))),然后遍历排序后的数组,只检查相邻的汽车 (O (2 * n))。您可以这样做,因为如果相邻汽车不满足 10 米条件,则非相邻汽车也不能满足。这将为您提供以下复杂性:

O (n * log(n))

这要好得多。排序后的汽车数组自然会为您提供需要应用硝基增压规则的最后位置的汽车。可能还有其他优化。这回答了你的问题:

对于每个实例,我是否应该检查每对驱动程序的所有 C(n,2) 组合并计算结果?

【讨论】:

  • 对不起,如果我误解了,排序操作必须在每个实例(每 2 秒)进行一次?
  • @shole 是的,每次迭代都必须进行排序操作。
【解决方案2】:

汽车对象和游戏对象

我假设您已经为汽车创建了必要的对象,封装在一个游戏对象中。

加快每个更新步骤以不进行所有 C(n,2) 检查的想法

您可以加快更新位置和参数的步骤,而不必检查所有 C(n,2) 组合。您可以使用的一种简单启发式方法是,我们不需要检查远处汽车之间的交互。例如,赛道第一节中的汽车不会与赛道第三节中的赛车互动。我认为根据您问题的参数,您希望将赛道分成 10m 长的部分。维护每个部分的列表并跟踪每个部分中的所有汽车。更新位置后,仅检查连续路段中汽车之间的交互。

同样,在每个更新步骤中跟踪哪辆车处于最后位置,并相应地切换硝基助推器。

时间步长的选择

在您的问题中,timeStep 似乎固定为 2 秒。但是,在一般情况下,例如,当您编写游戏时,此选择是至关重要的。您可以使用几个不同的数字(例如 10,50,100,500 毫秒)。

如果您将 timeStep 选择为较大的数字,(例如)该车可能会通过另一辆车并避免检测到碰撞。 另一方面,如果您选择的 timeStep 太小,并且操作所花费的时间大于 timeStep,则游戏将运行得比实时慢。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-22
    • 1970-01-01
    • 1970-01-01
    • 2022-09-29
    • 2022-12-16
    • 2020-10-03
    相关资源
    最近更新 更多