Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
商人过河问题扩展
之前的文章见:
将算法应用于实际——商人过河问题 - 掘金 (juejin.cn) 商人过河:如何使用图算法求出所有的解? - 掘金 (juejin.cn)
从一条河变成两条河
我们把商人过河问题进行进一步的扩展,把一条河变成两条河。
这是问题就变为了:名商人和名随从乘船渡河,一共有两条河,划分为三条岸,只有到达最右边的岸才算过河。现此岸有一小船只能容纳人,由他们自己划行。若在河的任一岸随从人数比商人多,他们就可能抢劫财物。不过如何乘船渡河的大权由商人们掌握。商人们怎样才能携带所有随从安全过河呢?
怎么表示状态?
先设三条岸为左岸(岸0)、中岸(岸1)、右岸(岸2);
如果我们仍然用之前的三个变量的方式来表示状态(其中为分别表示船在左岸、中岸、右岸):
这个状态表示了左岸有一个商人、没有随从、船在右岸。但是怎么确定剩下的人有多少在中岸,有多少在右岸呢?
因此我们只能扩充维度,用5个变量来确定唯一的状态。有如下定义:
表示左岸有个商人、个随从,中岸有个商人、个随从,船在岸处。
那么右岸的人数就可轻易推断出,从而表示了唯一的状态。
前面四个状态可以用来表示3条岸中任意两台的人数,剩下那一条都可以推断出来。
时间复杂度过高了!!
现在如果仍然使用DFS的话,时间复杂度会不敢想象。因为在第一节中,我的DFS经过特殊处理使得虽然能输出所有的路径,但是时间复杂度是以指数级增长的!
因此我们退而求其次,只求出最短的一条可行路径之一。
这时时间复杂度得到了极大的优化:我们使用Dijkstra算法,使得若图的顶点数为,边数为时,复杂度为 。
这次我还优化了程序的输出效果:
请分别输入商人数、随从数、船只容量:
5 5 2
有解,最短次数:34
依次的状态:
商人 随从 |河| 商人 随从 |河| 商人 随从
-------------------------------------
5 5 | | 0 0 | | 0 0
-------------------------------------
4 4 | | 1 -- 1 | | 0 0
-------------------------------------
4 4 | | 0 0 | | 1 -- 1
-------------------------------------
4 4 | | 1 -- 0 | | 0 1
-------------------------------------
5 -- 4 | | 0 0 | | 0 1
-------------------------------------
4 3 | | 1 -- 1 | | 0 1
-------------------------------------
...
-------------------------------------
0 0 | | 0 2 | | 5 -- 3
-------------------------------------
0 0 | | 0 -- 3 | | 5 2
-------------------------------------
0 0 | | 0 1 | | 5 -- 4
-------------------------------------
0 0 | | 1 -- 1 | | 4 4
-------------------------------------
0 0 | | 0 0 | | 5 -- 5
-------------------------------------
完整代码
/*
扩展版本:两条河
需要考虑如何储存状态
设有n个岸
变量:m1,f1,m2,f2,b,
m1/f1指岸1的人数
m2/f2指岸2的人数
b是船的停靠位置
岸3的人数便可以推断出来
因此可以表达一个唯一的状态
状态的转移照往常一样
*/
#include <iostream>
#include <vector>
#include <queue>
#include <numeric>
#include <string.h>
using std::cin;
using std::cout;
using std::fill_n;
using std::priority_queue;
using std::string;
using std::vector;
const int maxn = 50;
const int inf = 0x3f3f3f3f;
struct node
{
int m1;
int f1;
int m2;
int f2;
int b;
};
struct Dist
{
int m1;
int f1;
int m2;
int f2;
int b;
int dist;
bool operator<(const Dist &a) const
{
return dist > a.dist;
}
};
int found[maxn][maxn][maxn][maxn][3];
int able[maxn][maxn][maxn][maxn];
vector<node> graph[maxn][maxn][maxn][maxn][3];
int dist[maxn][maxn][maxn][maxn][3];
node last[maxn][maxn][maxn][maxn][3];
int mNum, fNum, bCap;
//保证状态合法
void AddEdge(int m1, int f1, int m2, int f2)
{
int m3 = mNum - m1 - m2, f3 = fNum - f1 - f2;
for (int b = 0; b <= 2; b++)
for (int dm = 0; dm <= bCap; dm++)
for (int df = 0; df + dm <= bCap; df++)
{
if (df + dm == 0)
continue;
int ml, fl, mr, fr, m1t, f1t, m2t, f2t, b1;
if (b != 0)
{
if (b == 1)
m1t = ml = m1 + dm,
f1t = fl = f1 + df,
m2t = mr = m2 - dm,
f2t = fr = f2 - df,
b1 = 0;
else
m1t = m1,
f1t = f1,
m2t = ml = m2 + dm,
f2t = fl = f2 + df,
mr = m3 - dm,
fr = f3 - df,
b1 = 1;
if (ml <= mNum && fl <= fNum && mr >= 0 && fr >= 0 && able[m1t][f1t][m2t][f2t])
graph[m1][f1][m2][f2][b].push_back(node{m1t, f1t, m2t, f2t, b1});
}
if (b != 2)
{
if (b == 0)
m1t = ml = m1 - dm,
f1t = fl = f1 - df,
m2t = mr = m2 + dm,
f2t = fr = f2 + df,
b1 = 1;
else if (b == 1)
m1t = m1,
f1t = f1,
m2t = ml = m2 - dm,
f2t = fl = f2 - df,
mr = m3 + dm,
fr = f3 + df,
b1 = 2;
if (mr <= mNum && fr <= fNum && ml >= 0 && fl >= 0 && able[m1t][f1t][m2t][f2t])
graph[m1][f1][m2][f2][b].push_back(node{m1t, f1t, m2t, f2t, b1});
}
}
}
void SetEnableState()
{
for (int m1 = 0; m1 <= mNum; m1++)
for (int f1 = 0; f1 <= fNum; f1++)
for (int m2 = 0; m2 + m1 <= mNum; m2++)
for (int f2 = 0; f2 + f1 <= fNum; f2++)
{
int m3 = mNum - m1 - m2, f3 = fNum - f1 - f2;
//任何一边,商人比随从多或者商人为0为可行状态
if ((m1 >= f1 || m1 == 0) && (m2 >= f2 || m2 == 0) && (m3 >= f3 || m3 == 0))
able[m1][f1][m2][f2] = 1;
}
}
void SetGraph()
{
for (int m1 = 0; m1 <= mNum; m1++)
for (int f1 = 0; f1 <= fNum; f1++)
for (int m2 = 0; m2 + m1 <= mNum; m2++)
for (int f2 = 0; f2 + f1 <= fNum; f2++)
if (able[m1][f1][m2][f2])
AddEdge(m1, f1, m2, f2);
}
void Dijkstra()
{
priority_queue<Dist> q;
memset(dist, inf, sizeof(dist));
dist[mNum][fNum][0][0][0] = 0;
q.push(Dist{mNum, fNum, 0, 0, 0, 0});
while (!q.empty())
{
auto tmp = q.top();
int m1 = tmp.m1, f1 = tmp.f1, m2 = tmp.m2, f2 = tmp.f2, b = tmp.b, d = tmp.dist;
q.pop();
found[m1][f1][m2][f2][b] = 1;
for (auto v : graph[m1][f1][m2][f2][b])
{
int m1t = v.m1, f1t = v.f1, m2t = v.m2, f2t = v.f2, bt = v.b;
if (d + 1 < dist[m1t][f1t][m2t][f2t][bt])
{
last[m1t][f1t][m2t][f2t][bt] = {m1, f1, m2, f2, b};
dist[m1t][f1t][m2t][f2t][bt] = d + 1;
q.push(Dist{m1t, f1t, m2t, f2t, bt, d + 1});
}
}
while (!q.empty())
{
auto tmp = q.top();
int m1t = tmp.m1, f1t = tmp.f1, m2t = tmp.m2, f2t = tmp.f2, bt = tmp.b;
if (found[m1t][f1t][m2t][f2t][bt])
q.pop();
else
break;
}
}
}
void recur(int m1, int f1, int m2, int f2, int b)
{
if (m1 == mNum && f1 == fNum && b == 0)
{
printf(" %d %d | | %d %d | | %d %d \n", m1, f1, m2, f2, mNum - m1 - m2, fNum - f1 - f2);
printf("-------------------------------------\n");
return;
}
node tmp = last[m1][f1][m2][f2][b];
int m1t = tmp.m1, f1t = tmp.f1, m2t = tmp.m2, f2t = tmp.f2, bt = tmp.b;
recur(m1t, f1t, m2t, f2t, bt);
if (b == 0)
printf(" %d -- %d | | %d %d | | %d %d \n", m1, f1, m2, f2, mNum - m1 - m2, fNum - f1 - f2);
else if (b == 1)
printf(" %d %d | | %d -- %d | | %d %d \n", m1, f1, m2, f2, mNum - m1 - m2, fNum - f1 - f2);
else
printf(" %d %d | | %d %d | | %d -- %d \n", m1, f1, m2, f2, mNum - m1 - m2, fNum - f1 - f2);
printf("-------------------------------------\n");
return;
}
int main()
{
cout << "请分别输入商人数、随从数、船只容量:\n";
cin >> mNum >> fNum >> bCap;
//设置可行状态
SetEnableState();
//连边
SetGraph();
Dijkstra();
if (dist[0][0][0][0][2] != inf)
{
cout << "有解,最短次数:" << dist[0][0][0][0][2] << " \n依次的状态:\n";
cout << "商人 随从 |河| 商人 随从 |河| 商人 随从\n";
cout << "-------------------------------------\n";
recur(0, 0, 0, 0, 2);
}
else
cout << "无解!";
return 0;
}