四个矿工必须通过一条隧道。他们只有一盏提灯,隧道一次只能通过两人,而在通过隧道时必须提着提灯。他们不能在隧道里停下。一个矿工能用一分钟通过隧道,而其他人分别需要2、4和8分钟。当两个矿工一起通过时,只能按比较慢的矿工的速度前行。请问如何让四个矿工在15分钟内全部通过?请解释你的解决方案。
一看这样的题目,就是深度搜索加剪枝。
C++代码(为了更直观,都放在一个文件里,实际中应该分头文件和源文件)
#include <iostream>
#include <deque>
#include <functional>
#include <algorithm>
#include<cmath>
using namespace std;
const int maxTotalTime = 15; //总共耗时限制
struct MinersState
{
int miners[4] = { 1, 2, 4, 8 }; //1, 2, 4, 8分别表示4个矿工的耗时
bool bLocal = true; //true表示在本地,false表示去了对面
int lastTime = 0; //初始为0,这个变量表示需要的时间
//最终状态,全部为负表示都去了对面
bool IsFinalState()
{
if ((miners[0] < 0) && (miners[1] < 0) && (miners[2] < 0) && (miners[3] < 0)
&& (bLocal == false))
{
return true;
}
return false;
}
//打印当前位置
void PrintStates()
{
cout << "[";
for (size_t i = 0; i < 4; i++)
{
cout << miners[i] << ",";
}
cout << "]";
if (bLocal) {
cout << " light is here" << endl;
}
else
{
cout << " light is there" << endl;
}
}
//是否是相同位置(包括矿工和灯的位置)
bool IsSameState(const MinersState& state) const
{
if ((miners[0] == state.miners[0]) &&
(miners[1] == state.miners[1] &&
(miners[2] == state.miners[2]) &&
(miners[3] == state.miners[3]) &&
bLocal == state.bLocal))
{
return true;
}
return false;
}
};
void SearchState(deque<MinersState>& states);
void PrintResult(deque<MinersState>& states)
{
cout << "Find Result : " << endl;
for_each(states.begin(), states.end(),
mem_fun_ref(&MinersState::PrintStates));
cout << endl << endl;
}
int calTotalTime(deque<MinersState>& states)
{
int sum = 0;
for (size_t i = 0; i < states.size(); ++i)
{
sum += states.at(i).lastTime;
}
return sum;
}
bool IsCurrentActionValid(deque<MinersState>& states, MinersState& next, int pos0, int pos1)
{
if ( pos0 >= 0 && pos0 <= 3 && pos1 < 0 )
{
if ( (next.bLocal && (next.miners[pos0] > 0)) ||
(!next.bLocal && (next.miners[pos0] < 0))) //灯和带灯的矿工在隧道的同一边
{
int sum = calTotalTime(states);
sum += next.lastTime;
if (sum <= maxTotalTime)
{
return true;
}
}
}
if ((pos0 >= 0 && pos0 <= 3) && (pos1 >= 0 && pos1 <= 3))
{
if ((next.bLocal && next.miners[pos0] > 0 && next.miners[pos1] > 0) ||
(!next.bLocal && next.miners[pos0] < 0 && next.miners[pos1] < 0)) //这次是这边
{
int sum = calTotalTime(states);
sum += next.lastTime;
if (sum <= maxTotalTime)
{
return true;
}
}
}
//灯在这边,所有矿工却都在对面,这种情况不允许发生(多虑了)
if ((next.bLocal && next.miners[0] < 0 && next.miners[1] < 0 && next.miners[2] < 0 && next.miners[3] < 0) ||
(!next.bLocal && next.miners[0] > 0 && next.miners[1] > 0 && next.miners[2] > 0 && next.miners[3] > 0))
{
return false;
}
return false;
}
bool IsSameBucketState(MinersState state1, MinersState state2)
{
return state1.IsSameState(state2);
}
//是否已经处理过?
bool IsProcessedState(deque<MinersState>& states, const MinersState& newState)
{
deque<MinersState>::iterator it = states.end();
it = find_if(states.begin(), states.end(),
bind2nd(ptr_fun(IsSameBucketState), newState));
return (it != states.end());
}
void SearchStateOnAction(deque<MinersState>& states, MinersState& current, int pos0, int pos1)
{
MinersState next;
next.bLocal = !current.bLocal; //每次过隧道,灯的位置就要改变
for (size_t i = 0; i < 4; i++)
{
next.miners[i] = current.miners[i];
}
if (pos0 >= 0 && pos0 <= 3)
{
next.miners[pos0] = -next.miners[pos0]; //每次符号变换,表示从隧道一头到另外一头
next.lastTime = abs(next.miners[pos0]);
}
if (pos1 >= 0 && pos1 <= 3)
{
next.miners[pos1] = -next.miners[pos1];
next.lastTime = abs(next.miners[pos1]);
}
//pos0和pos1都在0~3范围内,说明是2个矿工一起过隧道,取两者过隧道绝对值大的,作为2者过隧道的总时间
if ((pos0 >= 0 && pos0 <= 3) && (pos1 >= 0 && pos1 <= 3))
{
next.lastTime = (abs(next.miners[pos0]) > abs(next.miners[pos1])) ? abs(next.miners[pos0]) : abs(next.miners[pos1]);
}
if (IsCurrentActionValid(states, next, pos0, pos1))
{
if (!IsProcessedState(states, next))
{
states.push_back(next);
SearchState(states);
states.pop_back();
}
}
}
void SearchState(deque<MinersState>& states)
{
MinersState current = states.back();
if (current.IsFinalState())
{
PrintResult(states);
return;
}
//2个人过去,一个人把灯带回来;然后两个人过去,重复这个过程...
if (current.bLocal) //灯在这边
{
//从正数中选2个,正数表示还还没过隧道的矿工
for (int i = 0; i < 3; ++i)
{
for (int j = i+1; j < 4; ++j)
{
if ((current.miners[i] > 0) && current.miners[j] > 0)
{
SearchStateOnAction(states, current, i, j);
}
}
}
}
else
{
//从负数中选1,负数表示隧道对面的矿工
for (int i = 0; i < 4; ++i)
{
if (current.miners[i] < 0)
{
SearchStateOnAction(states, current, i, -1);
}
}
}
}
int main()
{
deque<MinersState> states;
MinersState init;
states.push_back(init);
SearchState(states);
cout << "seaching finished ---" << endl;
return 0;
}
运行结果:
解释:有两种方案:A:1,B:2,C:4;D:8,这四位矿工。
方案一:(1)A和B过去,耗时2分钟
(2)A带着灯返回,耗时1分钟;
(3)C和D带着灯过隧道,耗时8分钟;
(4)B带着灯返回,耗时2分钟;
(5)A和B一起通过隧道,到达目的地,耗时2分钟。
总耗时:2 + 1 + 8 + 2 + 2 = 15
方案二:(1)A和B过去,耗时2分钟
(2)B带着灯返回,耗时2分钟;
(3)C和D带着灯过隧道,耗时8分钟;
(4)A带着灯返回,耗时1分钟;
(5)A和B一起通过隧道,到达目的地,耗时2分钟。
总耗时:2 + 2 + 8 + 1 + 2 = 15