商人过河:河神二话不说又多变了一条河出来......

657 阅读4分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

商人过河问题扩展

之前的文章见:

将算法应用于实际——商人过河问题 - 掘金 (juejin.cn) 商人过河:如何使用图算法求出所有的解? - 掘金 (juejin.cn)

从一条河变成两条河

我们把商人过河问题进行进一步的扩展,把一条河变成两条河。

这是问题就变为了:mm名商人和ff名随从乘船渡河,一共有两条河,划分为三条岸,只有到达最右边的岸才算过河。现此岸有一小船只能容纳bb人,由他们自己划行。若在河的任一岸随从人数比商人多,他们就可能抢劫财物。不过如何乘船渡河的大权由商人们掌握。商人们怎样才能携带所有随从安全过河呢?

怎么表示状态?

先设三条岸为左岸(岸0)、中岸(岸1)、右岸(岸2);

如果我们仍然用之前的三个变量的方式来表示状态(其中bb0120、1、2分别表示船在左岸、中岸、右岸):

(1,0,2)(1,0,2)

这个状态表示了左岸有一个商人、没有随从、船在右岸。但是怎么确定剩下的人有多少在中岸,有多少在右岸呢?

因此我们只能扩充维度,用5个变量来确定唯一的状态。有如下定义:

(m1,f1,m2,f2,b)(m1,f1,m2,f2,b)

表示左岸有m1m1个商人、f1f1个随从,中岸有m2m2个商人、f2f2个随从,船在岸bb处。

那么右岸的人数就可轻易推断出,从而表示了唯一的状态。

前面四个状态可以用来表示3条岸中任意两台的人数,剩下那一条都可以推断出来。

时间复杂度过高了!!

现在如果仍然使用DFS的话,时间复杂度会不敢想象。因为在第一节中,我的DFS经过特殊处理使得虽然能输出所有的路径,但是时间复杂度是以指数级增长的!

因此我们退而求其次,只求出最短的一条可行路径之一。

这时时间复杂度得到了极大的优化:我们使用Dijkstra算法,使得若图的顶点数为VV,边数为EE时,复杂度为 O(ElgV)O(ElgV)


这次我还优化了程序的输出效果:

请分别输入商人数、随从数、船只容量:
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;
}