火车站最少停靠次数

Posted

技术标签:

【中文标题】火车站最少停靠次数【英文标题】:Minimum number of train station stops 【发布时间】:2018-07-10 04:24:47 【问题描述】:

我收到了这个面试问题并被卡住了:

从 0 号车站开始,有无数个火车停靠站。

火车的数量是无限的。第 n 列火车在 k 介于 0 和无穷大之间的所有 k * 2^(n - 1) 个站点停靠。

当n = 1时,首班列车停靠在0、1、2、3、4、5、6等站点

当n = 2时,第二列火车停靠在0、2、4、6、8等站点

当n = 3时,第三列火车停靠在0、4、8、12等站台

给定起点站号和终点站号,返回它们之间的最小停靠点数。您可以使用任何一种列车从一站到另一站。

例如,start = 1 和 end = 4 之间的最小停靠点数是 3,因为我们可以从 1 到 2 再到 4。

我正在考虑一种动态编程解决方案,它将在dp[start][end] 中存储startend 之间的最小步数。我们将使用start...mid1, mid1...mid2, mid2...mid3, ..., midn...end 构建数组。但我无法让它工作。你如何解决这个问题?

说明:

    火车只能从编号较低的车站前进到编号较高的车站。 火车可以在任何停靠站发车。 可以按任何顺序搭乘火车。 n = 1 列车可以在登上 n = 3 列车之前或之后登上。 可以多次搭乘火车。例如,允许上n=1次列车,下一次上n=2次列车,最后再次上n=1次列车。

【问题讨论】:

任何火车都可以在任何车站发车吗? @masoudkeshavarz 火车无法启动任何车站。但是火车可以从任何停靠站开始。 在赶上n=3 火车后,你能赶上n=1 火车吗?还是赶上n=3火车后才能赶上n=4,5,6,....,n_i火车? @spacetyper 你可以按任何顺序赶火车。您可以在搭乘 n = 3 列车之前或之后搭乘 n = 1 列车。 正如其他发帖者所指出的,鉴于火车只能向前行驶,贪婪的方法显然是最佳解决方案。如果火车可以前进或后退,您可以在停靠点阵列上进行 DP。 minSteps[end] = 0minSteps[n] = min minSteps[n ± 2^m] | m ∊ ℕ + 1。按照增加所有mminSteps 值的顺序进行,直到m 是不超过end - start 的2 的最小幂,直到您分配给minSteps[start]。事实上,这种方法表明您可以使用 BFS 遍历。该图看起来像一个 FFT 蝴蝶。 【参考方案1】:

我认为你根本不需要动态编程来解决这个问题。基本可以用二进制计算来表示。

如果您将站点编号转换为二进制,它会立即告诉您如何从站点 0 到达那里,例如,

站 6 = 110

告诉您需要乘坐 n=3 次列车和 n=2 次列车各一站。所以二进制表示的popcount 告诉你需要多少步。

下一步是弄清楚如何从一个站点到达另一个站点。 我将通过示例再次展示这一点。假设你想从 7 号站到 23 号站。

7 号站 = 00111

23 号站 = 10111

您要做的第一件事是到达中间站。此停靠点由

指定

(起始站和结束站中相同的最高位)+(第一个不同的位)+(用零填充)

在我们的示例中,中间停止点是 16 (10000)。您需要执行的步骤可以通过该数字与起始站(7 = 00111)的差来计算。在我们的示例中,这会产生

10000 - 00111 = 1001

现在您知道,从 7 站到 16 站需要 2 个站点(n=1 列火车和 n=4)。 剩下的任务是从 16 到 23,这又可以通过对应的差来解决

10111 - 10000 = 00111

所以,从 16 到 23(n= 3,n= 2,n= 1),您还需要 3 站。这总共为您提供 5 个停靠点,仅使用两个二进制差异和 popcount。生成的路径可以从位表示中提取 7 -> 8 -> 16 -> 20 -> 22 -> 23

编辑:

为了进一步说明中间停靠点,我们假设我们想要从

站 5 = 101 到

7 号站 = 111

在这种情况下,中间停靠点将是 110,因为

起始站和结束站中相等的最高位 = 1

第一个不同的位 = 1

用零填充 = 0

我们需要一步才能到达那里 (110 - 101 = 001),然后再走一步才能到达终点站 (111 - 110 = 001)。

关于中间站

中间停止的概念有点笨拙,但我找不到更优雅的方式来让位操作工作。中间停止是开始和结束之间***别位切换的停止(这就是它的构造方式的原因)。在这方面,它是最快的火车(在起点和终点之间)运行的站点(实际上您能赶上的所有火车都停在那里)。

通过从终端站(位表示)中减去中间停止(位表示),您可以将问题简化为从站 0 开始的简单情况(参见我的答案的第一个示例)。

通过从中间站减去起点站,您也可以将问题简化为简单的情况,但假设您从中间站到起点站,这相当于反过来。

【讨论】:

这个分析有漏洞;例如,从 5 号站到 7 号站,此分析将尝试在 4 号站进行中间停止,因为 4 号站对应于数字 7 中的最高设置位。 确实,感谢您指出这一点。我不得不引入一个中间停止来解决这个问题,但现在它应该可以工作了。 @SaiBot 您能否解释一下为什么中间停止点由“(起点和终点站中相等的最高位)+(第一个不同的位)+(用零填充)”指定? @user9292787,添加了解释,希望它使中间停止更清晰。 这是正确的,但是将“在每个车站,乘坐不超过目的地的最高火车”的公式化为***.com/a/48536078/1400793,这是一种非常复杂的方式。该答案还附带一个简短的正确性证明,我认为这是更好的解决方案。【参考方案2】:

首先,问你是否可以倒退。听起来你不能,但正如这里所介绍的(这可能不会反映你收到的问题),这个问题从来没有为这些火车中的任何一个给出明确的方向。 (我看到你现在已经编辑了你的问题,说你不能倒退。)

假设您不能倒退,策略很简单:始终乘坐不会超过您的目的地的最高编号的可用火车。

假设您停在s,而停在您当前位置且没有超车的编号最高的火车是火车k。在火车上旅行一次k 将带您停止s + 2^(k-1)。没有更快的方法可以到达那个站点,也没有办法跳过那个站点 - 没有编号较低的火车跳过任何火车 k 的站点,也没有编号较高的火车在火车 k 的站点之间停靠,所以在你到达那里之前你不能上更高号的火车。因此,train k 是你最好的直接行动。

考虑到这种策略,剩下的大部分优化都是通过有效的小技巧来计算停靠点的数量,而无需明确计算出路线上的每个停靠点。

【讨论】:

【参考方案3】:

我会尝试证明我的算法是最优的。

算法是“乘坐不会超过目的地的最快火车”。

这是多少站有点棘手。

将两个站点编码为二进制数。我声称可以忽略相同的前缀;从ab 的问题与从a+2^nb+2^n 如果2^n > b 的问题相同,因为2^n2^(n+1) 之间的停靠点只是@ 之间的停靠点987654328@ 和 2^n 转移了。

由此,我们可以减少从ab 的行程,以保证设置b 的高位,而a 的相同“高”位是不 em> 设置。

要解决从 5 (101) 到 7 (111) 的问题,我们只需解决从 1 (01) 到 3 (11) 的问题,然后将我们的停止编号上移 4 ( 100)。

要从x2^n + y,其中y < 2^n(因此x 是),我们首先想去2^n,因为没有火车可以跳过2^n也不要跳过2^n+y < 2^n+1

因此,xy 之间的任何一组停靠点都必须在 2^n 处停靠。

因此,从x2^n + y 的最佳停靠点数是从x2^n 的停靠点数,然后是从2^n2^n+y 的停靠点数,包括(或从0y,同理)。

我建议从0y 的算法是从高位集合开始,然后乘坐火车到达那里,然后继续往下走。

声明:要生成带有k1s 的号码,您必须至少乘坐k 火车。作为证明,如果您乘坐火车并且它不会导致您的停靠站号出现进位,则它会设置 1 位。如果您乘坐火车并且它确实会导致进位,则生成的数字最多比开始时多 1 个设置位。

x2^n 有点棘手,但可以通过跟踪您乘坐的火车向后变得简单。

s_i 映射到s_2^n-i 并反转列车步骤,从x2^n 的任何解决方案都描述了从02^n-x 的解决方案。任何对前向最优的解决方案对后向最优,反之亦然。

使用从0y 的结果,然后我们得到从ab 的最佳路线,其中b 最高位设置为2^na 没有该位设置为#b-2^n + #2^n-a,其中# 表示“二进制表示中设置的位数”。一般来说,如果ab 有一个共同的前缀,只需去掉那个共同的前缀。

产生上述步数的当地规则是“在您当前位置乘坐不超过目的地的最快火车”。

对于从2^n2^n+y 的部分,我们在上面的证明中明确地做到了这一点。对于从x2^n 的部分,这很难看。

首先,如果设置了x 的低位,显然我们必须乘坐我们可以乘坐的第一班也是唯一一班火车。

其次,想象x 有一些未设置的低位集合,比如m。如果我们玩从x/2^m2^(n-m) 的火车游戏,然后通过乘以2^m 来缩放停靠站编号,我们将得到从x2^n 的解决方案。

还有#(2^n-x)/2^m = #2^n - x。所以这种“规模化”的解决方案是最优的。

由此,在这个最优解中,我们总是乘坐与我们的低阶设置位相对应的火车。这是可用的最远距离列车,并且不会超过2^n

QED

【讨论】:

【参考方案4】:

这个问题不需要动态规划。

这是一个使用 GCC 的解决方案的简单实现:

uint32_t min_stops(uint32_t start, uint32_t end)

    uint32_t stops = 0;
    if(start != 0) 
        while(start <= end - (1U << __builtin_ctz(start))) 
            start += 1U << __builtin_ctz(start);
            ++stops;
        
    
    stops += __builtin_popcount(end ^ start);
    return stops;

火车模式是二次方的映射。如果您将火车线可视化为位表示,您可以看到最低位集表示您可以乘坐的车站之间距离最长的火车线。您也可以选择距离较短的线路。

为了尽量缩短距离,您希望选择距离尽可能远的线路,直到终点站无法到达为止。这就是在代码中添加最低设置位的作用。一旦你这样做了,一些高位将与终端站的高位一致,而低位将为零。

到那时,只需在当前车站中没有设置的终点站最高位乘坐火车即可。这在代码中优化为__builtin_popcount

从 5 到 39 的示例:

000101 5        // Start
000110 5+1=6
001000 6+2=8
010000 8+8=16
100000 16+16=32 // 32+32 > 39, so start reversing the process
100100 32+4=36  // Optimized with __builtin_popcount in code
100110 36+2=38  // Optimized with __builtin_popcount in code
100111 38+1=39  // Optimized with __builtin_popcount in code

【讨论】:

这主要是可行的,但您忘记考虑start 可能没有任何设置位的可能性;如果start0,则__builtin_ctz(start) 未定义。 (此外,start + (1U &lt;&lt; __builtin_ctz(start)) 可能会立即或在几次火车旅行后溢出。)【参考方案5】:

正如一些人所指出的那样,由于停靠点都是 2 的倍数,因此停靠更频繁的列车也会停靠在较快列车的相同停靠点。任何站点都在第一列火车的路线上,该路线在每个车站都停靠。任何站点距离第二列火车的路线最多 1 个单位,每隔一站停靠一次。任何一站距离每四站停靠的第三列火车最多 3 个单位,依此类推。

因此,从最后开始并及时追溯您的路线 - 跳上最近的 2 倍数火车,并尽快切换到最高的 2 倍数火车尽可能(检查最低有效设置位的位置 - 为什么?2 的幂的倍数可以除以 2,即右移,不留余数,对数 2 次,或在该位中尽可能多的前导零-representation),只要它的间隔不会错过一站后的起点。如果是后者,则执行倒车切换,跳上下一个较低的 2 次方倍数列车并一直在其上停留,直到其间隔不会错过一站后的起点,依此类推。

【讨论】:

【参考方案6】:

我们只需要一点计数和数组操作就可以解决这个问题。像之前的所有答案一样,我们需要首先将两个数字转换为二进制并将它们填充到相同的长度。所以 12 和 38 变成 01100 和 10110。

查看第 12 站,查看最低有效设置位(在本例中为唯一位,2^2)所有间隔大于 2^2 的列车都不会在第 4 站停靠,所有间隔小于或等于 2^2 将在第 4 站停靠,但需要多次停靠才能到达与间隔 4 列车相同的目的地。我们在每一种情况下,直到我们到达最终值中的最大设置位,我们都需要以当前站的最低有效位的间隔乘坐火车。

如果我们在 0010110100 站,我们的序列将是:

0010110100  2^2
0010111000  2^3
0011000000  2^6
0100000000  2^7
1000000000

在这里我们可以消除所有小于最低有效位的位并获得相同的计数。

00101101  2^0
00101110  2^1
00110000  2^4
01000000  2^6
10000000

在每个阶段修剪末端,我们得到:

00101101  2^0
 0010111  2^0
    0011  2^0
      01  2^0
       1

这同样可以描述为翻转所有 0 位的过程。这将我们带到算法的前半部分:计算大于最低有效设置位的零填充起始编号中的未设置位,如果起始站为 0,则为 1。

这将使我们到达火车可以到达的唯一中间站,其最大间隔小于终点站,因此此后的所有火车都必须小于前一列。

现在我们需要从车站到100101,更简单明了,乘坐间隔等于目的地中设置的最大有效位而不是当前站号中设置的火车。

1000000000 2^7
1010000000 2^5
1010100000 2^4
1010110000 2^2
1010110100

与第一种方法类似,我们可以修剪将始终设置的最高有效位,然后计算答案中剩余的 1。所以算法的第二部分是计算所有设置的比最高有效位小的有效位

然后将第 1 部分和第 2 部分的结果相加

稍微调整算法以获得所有的火车间隔,这是一个用javascript编写的示例,因此可以在此处运行。

function calculateStops(start, end) 
  var result = 
    start: start,
    end: end,
  	count: 0,
  	trains: [],
    reverse: false
  ;
  
  // If equal there are 0 stops
  if (start === end) return result;

  // If start is greater than end, reverse the values and
  // add note to reverse the results
  if (start > end) 
    start = result.end;
    end = result.start;
    result.reverse = true;
      

  // Convert start and end values to array of binary bits
  // with the exponent matched to the index of the array
  start = (start >>> 0).toString(2).split('').reverse();
  end = (end >>> 0).toString(2).split('').reverse();

  // We can trim off any matching significant digits
  // The stop pattern for 10 to 13 is the same as
  // the stop pattern for 2 to 5 offset by 8
	while (start[end.length-1] === end[end.length-1]) 
    start.pop();
    end.pop();
  

  // Trim off the most sigificant bit of the end, 
  // we don't need it
  end.pop();

  // Front fill zeros on the starting value
  // to make the counting easier
  while (start.length < end.length) 
    start.push('0');
  

  // We can break the algorithm in half
  // getting from the start value to the form
  // 10...0 with only 1 bit set and then getting
  // from that point to the end.

  var index;
	var trains = [];
  var expected = '1';

  // Now we loop through the digits on the end
  // any 1 we find can be added to a temporary array
  for (index in end) 
    if (end[index] === expected)
    	result.count++;
      trains.push(Math.pow(2, index));
    ;
  

  // if the start value is 0, we can get to the 
  // intermediate step in one trip, so we can
  // just set this to 1, checking both start and
  // end because they can be reversed
  if (result.start == 0 || result.end == 0) 
    index++
    result.count++;
    result.trains.push(Math.pow(2, index));
  // We need to find the first '1' digit, then all
  // subsequent 0 digits, as these are the ones we
  // need to flip
   else 
    for (index in start) 
      if (start[index] === expected)
        result.count++;
        result.trains.push(Math.pow(2, index));
        expected = '0';
      
      
  

  // add the second set to the first set, reversing
  // it to get them in the right order.
	result.trains = result.trains.concat(trains.reverse());

  // Reverse the stop list if the trip is reversed
	if (result.reverse) result.trains = result.trains.reverse();
  
  return result;


$(document).ready(function () 
	$("#submit").click(function () 
  	var trains = calculateStops(
      parseInt($("#start").val()),
      parseInt($("#end").val())
    );

  	$("#out").html(trains.count);
    
  	var current = trains.start;
    var stopDetails = 'Starting at station ' + current + '<br/>';
    for (index in trains.trains) 
      current = trains.reverse ? current - trains.trains[index] : current + trains.trains[index];
      stopDetails = stopDetails + 'Take train with interval ' + trains.trains[index] + ' to station ' + current + '<br/>';
    

  	$("#stops").html(stopDetails);
  );
);
label 
  display: inline-block;
  width: 50px;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<label>Start</label> <input id="start" type="number" /> <br>
<label>End</label> <input id="end" type="number" /> <br>
<button id="submit">Submit</button>

<p>Shortest route contains <span id="out">0</span> stops</p>
<p id="stops"></p>

【讨论】:

【参考方案7】:

简单的 Java 解决方案

public static int minimumNumberOfStops(int start, final int end) 
    // I would initialize it with 0 but the example given in the question states :
    // the minimum number of stops between start = 1 and end = 4 is 3 because we can get from 1 to 2 to 4
    int stops = 1;
    while (start < end) 
        start += findClosestPowerOfTwoLessOrEqualThan(end - start);
        stops++;
    
    return stops;


private static int findClosestPowerOfTwoLessOrEqualThan(final int i) 
    if (i > 1) 
        return 2 << (30 - Integer.numberOfLeadingZeros(i));
    
    return 1;

【讨论】:

【参考方案8】:

注意:在我的回答下,当前 cmets 的原因是,首先我完全错误地编写了这个算法,并且 user2357112 从我的错误中意识到了我。所以我完全删除了该算法并根据 user2357112 对这个问题的回答编写了一个新算法。我还在该算法中添加了一些 cmets 以阐明每一行中发生的情况。

此算法从procedure main(Origin, Dest) 开始,它使用updateOrigin(Origin, Dest) 模拟我们向目的地的移动

procedure main(Origin, Dest)

         //at the end we have number of minimum steps in this variable
         counter = 0;

         while(Origin != Dest)

              //we simulate our movement toward destination with this
              Origin = updateOrigin(Origin, Dest);

              counter = counter + 1;

         



procedure updateOrigin(Origin, Dest)

    if (Origin == 1) return 2;

    //we must find which train pass from our origin, what comes out from this IF clause is NOT exact choice and we still have to do some calculation in future
    if (Origin == 0)

       //all trains pass from stop 0, thus we can choose our train according to destination
       n = Log2(Dest);

    else

       //its a good starting point to check if it pass from our origin
       n = Log2(Origin);

    

    //now lets choose exact train which pass from origin and doesn't overshoot destination
    counter = 0;
    do 
             temp = counter * 2 ^ (n - 1);

             //we have found suitable train
             if (temp == Origin)

                 //where we have moved to
                 return Origin + 2 ^ ( n - 1 );

             //we still don't know if this train pass from our origin
              elseif (temp < Origin)

                 counter = counter + 1;

             //lets check another train
              else 

                 n = n - 1;
                 counter  = 0;

             

    while(temp < origin)


【讨论】:

我可以暂时隐藏这个帖子吗?我发现了一些问题,我需要对此进行编辑 删除后编辑即可,编辑完成后重新添加即可。系统永远不会删除您的帖子,它只会对其他用户隐藏它 这有很多基本问题。看起来您没有考虑到中途换车的可能性,或者您没有考虑到哪些火车在哪里停靠,或者您从Steps 中的给定起点开始旅程。 @user2357112 感谢您抽出宝贵时间并在Steps@ 中通知我有关此问题的信息 @user2357112 我已经查看了所有内容,根据 PO 要求,我在此代码中找不到任何基本问题。 PO 没有询问过changing trains mid-journey。我们必须找到哪列火车停靠在哪里,因此我们可以找到从我们的起点经过的哪列火车。最后我们计算火车到达目的地的步数。这意味着journey starts at starting point

以上是关于火车站最少停靠次数的主要内容,如果未能解决你的问题,请参考以下文章

NOIP2013 车站分级

P1983 车站分级

p1983 车站分级

Noip2013(普及组) 车站分级

luogu P1983 车站分级

车站分级(NOIP2013 普及组第四题)