背景
今天第一天开学,下午上了算法的第一节直播课,老师出了几道题。其他的问题还好,有道过桥题我一看竟然没啥头绪??等下课我去问老师,老师先是把我当成问毕设的学生,解释之后到现在也没回我。我寻思着不能吊死在老师这啊,要上网多找几棵树,总算找到一个比较清晰的、我也能理解的(我蠢其他很多C++代码看不懂)题解,在这里分享并说出我的理解。
题目
4个人在晚上过一座小桥,过桥时必须要用到手电筒,只有一枚手电筒,每次最多只可以有两人通过, 4个人的过桥速度分别为1分钟、2分钟、5分钟、10分钟,试问怎样走能把时间缩短在17分钟?
问题化
共有N个人,每个人过桥时间为tn(1≤n≤N),每次最多能过两个人,并且要回来一个人,问最短过桥时间。
我最初的想法
最初只是想了最简单的4人情况,都没想出来。(原谅我是个菜鸡)
当初的想法是最好能让次大的和最大的一起过,这样就只花了最大的时间次大不用花时间,然后最小和次小就再过来。但是这样的话第一步之后还要有个人回来,回来谁这步就和不带人一样了啊,这就15分钟了后面还走P啊。
要不先让最大的带最小的,然后最小回来,这是11分钟,然后最小带次大,最小回来,这就6分钟了,已经17了,可恶!!!!
解析
之后在网上找了好多,终于找到了一个通用的解决方法,采用了一定的方法然后递归(只说递归我也不知道怎么去理解如何递归,其实这个像个套娃或者不知道算不算动态规划,还需要一些逻辑上的思考,这个逻辑是基础,想不出第一步这个问题就不知道怎么解决)。
先说4个人的解决方法
设A、B、C、D四人的时间为a、b、c、d,依次为最快、次快、次慢、最慢者,过桥步骤如下:
- AB先过 b(2)
- A回 a(1)
- CD过 d(10)
- B回 b(2)(此处可以看做一个结点)
- AB过 b(2)
总计2+1+10+2+2=17分钟,当当当当!
再说通类问题,上面的步骤有个结点,没错就是第四步。走到这一步(即最大的两个在右侧,最小或其他在左侧),已知可能最快的办法有两种,一种是上面的,还有一种就是两次都由最小的和最大的过桥然后最小的回来,那么到底选哪种呢?
最佳方案构造:以下是构造N个人(N≥1)过桥最佳方案的方法:
- 如果N=1、2,所有人直接过桥。
- 如果N=3,由最快的人往返一次把其他两人送过河。
- 如果N≥4,设A、B为走得最快和次快的旅行者,过桥所需时间分别为a、b;
而C、D为走得最慢和次慢的旅行者,过桥所需时间分别为c、d。那么
当2b>a+c时,使用模式一将C和D移动过桥;
当2b<a+c时,使用模式二将C和D移动过桥;
当2b=a+c时,使用模式一或模式二将C和D移动过桥。
这样就使问题转变为N-2个旅行者的情形,从而递归解决之。
……
A D → d
A ← a
A C → c
A ← a
……
也就是“由A护送到对岸,A返回”,称作“模式一”。
第n-2步: A B → b
第n-1步: A ← a
第n步: C D → d
第n+1步: B ← b
……
这个模式是“由A和B护送到对岸,A和B返回”,称作“模式二”。(即上述四人的方式)
至于为什么分辨的公式是2b与a+c比较呢,因为在达到同样的结点(即每两个最大的被送过去而其余在本处,两个这个数量好像跟每次只能送两个人有关,这里其实就把最费时间的两个人用对他们两个最省时间的方式送了过去)时,两种方法在耗时上的不同点就是一为a+c,二为2b。在达到同样的目标的情况下,看这两个值谁小,谁就是最快,这就是N人中最耗时两人的最快办法。
剩余的N-2个人同样是这种理解方式,F(N)=F(N-2)+t(最慢两人的最快用时),就这样套娃套下去。
下面上原贴中的C++代码
#include <vector>
int TravelBridge(std::vector<int> times){
// 假设时间数组已经排序,从小到大
size_t length = times.size();
if(length <= 2)
return times[length-1];
else if(length == 3){
return times[0] + times[1] + times[2];
}else{
int totaltime = 0;
int a = times[0];
int b = times[1];
int c = times[length-2];
int d = times[length-1];
if(b*2 < a + c){//说明最小带最大会慢,用模式二
times.erase(times.end()-1);
times.erase(times.end()-1);
totaltime += b + a + d + b + TravelBridge(times);
}else{//说明最小带最大会快,用第一种
times.erase(times.end()-1);
totaltime += z + a + TravelBridge(times);
}
return totaltime;
}
}
下面上自己撸的Java代码
import java.util.Vector;
/**
* @author 滑技工厂
* @version 1.0
* @ClassName Main
* @Description 过桥问题
* @date 2020/2/10
*/
public class Main {
public static Integer travelBridge(Vector<Integer> times) {
int length = times.size();
if (length <= 2) {
return times.get(length - 1);
} else if (length == 3) {
return times.get(0) + times.get(1) + times.get(2);
} else {
Integer a = times.get(0);
Integer b = times.get(1);
Integer c = times.get(length - 2);
Integer d = times.get(length - 1);
if (2 * b < a + c) {//最小带最大慢,采用先ab后cd模式二
times.remove(times.lastElement());
times.remove(times.lastElement());
return b + a + d + b + travelBridge(times);
} else {//采用模式一
times.remove(times.lastElement());
return d + a + travelBridge(times);
}
}
}
public static void main(String[] args) {
Vector<Integer> times = new Vector<>();
times.add(1);
times.add(2);
times.add(5);
times.add(10);
int total = travelBridge(times);
System.out.println(total);
}
}
到此结束,谢谢大家,喜欢的请点个赞或者关注,学生党尽量常更新。有什么建议请多评论。最后宠物化的我镇楼