離散數學:過河問題

過河問題(一)圖論方法

農夫過河問題

問題描述

一個農夫帶着一頭狼、一頭羊、一顆白菜過河。他面前只有一條船,只能容納他和一件物品,只有農夫會划船。如果農夫不在場,狼會喫羊、羊會喫白菜,農夫在場則不會。求將所有物品運到對岸的方案。

解題思路

根據物品的位置定義狀態,若在左岸記爲1,右岸記爲0,於是最終方案就是(1,1,1,1)-->(0,0,0,0)所經過的路徑。

1、定義狀態

2、列舉所有狀態(人、狼、羊、菜)

3、刪除不合理的狀態(狼和羊、羊和菜)

4、連邊(模擬一次渡河)

5、尋找路徑

尋找(1111)-->(0000)的邊,可以用尋路算法如bfs、dfs,如果要求最短路可以用最短路算法如bfs、Dijsktra等,當然這裏圖很簡單,可直接觀察出來。

複製代碼

(1111)-->(0101)-->(1101)-->(0001)-->(1011)-->(0010)-->(1010)-->(0000)(兩條最短路之一)
       左岸                                 右岸
1、人  狼  羊 花                           空
2、狼  花                                 人 羊
3、人  狼 花                               羊
4、花                                     人 狼  羊
5、人  羊  花                              狼
6、羊                                     人 花  狼
7、人 羊                                  狼 花
8、空                                     狼 花 人 羊   

複製代碼

傳教士與喫人惡魔的問題

問題描述

有三個傳教士和三個喫人惡魔要渡過一條河,河中有一條船,只能裝下兩個人。在任何地方(無論是岸邊還是船上),如果喫人惡魔數量多於傳教士數量,喫人惡魔就會喫掉傳教士。問:怎麼才能讓這些都安全過河?

解題思路

1、定義狀態

2、列舉所有狀態

3、刪除不合理狀態

4、連邊(模擬依次渡河變化)

5、尋找路徑

尋找(33 L 00)-->(00 R 33)的路徑

複製代碼

其中一條路徑
(33 L 00)-->(31 R 01)-->(32 L 01)-->(30 R 03)-->(31 L 02)-->(11 R 22)-->(22 L 01)-->(02 R 31)-->(03 L 30)-->(01 R 32)-->(02 L 31)-->(00 R 33)
1、兩個喫人惡魔過河
2、一個喫人惡魔回來
3、兩個喫人惡魔過河
4、一個喫人惡魔回來
5、兩個傳教士過河
6、一個傳教士和一個喫人惡魔回來
7、兩個傳教士回來
8、一個喫人惡魔回去
9、兩個喫人惡魔過河
10、一個喫人惡魔回去
11、兩個喫人惡魔過河

複製代碼

四人過橋問題

問題描述

在一個漆黑的夜裏,四位旅遊者來到一座狹窄而沒有護欄的橋邊,如果不借助手電筒的話,大家是無論也不敢過去。不幸的是四個人中只有一隻手電筒,而橋窄得只夠兩個人同時通過。如果各自單獨過橋得話,四個人所需要的時間分別是1、2、5、10分鐘,如果兩個人同時過橋,所需要的時間是較慢的那個人單獨行動時的時間。問:如何設計一個方案,讓四個人儘快過橋。

解題思路

與前面兩個相比,這次不僅要求方案,同時要求時間最短。 

同樣需要定義狀態,四個人+手電筒的位置

1、定義狀態

2、建圖

分爲每次通過一個人和每次兩個人,都是帶權無向邊。

(下面只連接了與(01111)的邊)

3、尋找最短路

尋找(L 1111)-->(R 0000)的最短路,即最短路算法中(01111)-->(10000)的最短路,以下是利用Dijstra算法的解決方法。

最終答案爲(2 + 1 + 10 + 2 + 2) = 17.

複製代碼

  1 #include<stdio.h>
  2 #include<iostream>
  3 #include<string>
  4 #include<queue>
  5 #include<algorithm>
  6 using namespace std;
  7 
  8 //定義圖中結點
  9 struct Node
 10 {
 11     int u, d;            //該節點的編號與距離
 12     bool operator < (const Node x) const
 13     {
 14         return  d > x.d;
 15     }
 16 };
 17 
 18 //邊結構體的定義
 19 struct Edge
 20 {
 21     int to;     
 22     int w;       
 23     int next;  
 24 };
 25 
 26 const int INF = 0x3f3f3f3f;
 27 const int V = 32 + 10;
 28 const int E = 32 * 32 + 10;
 29 int dis[V];            //源到各頂點的最短距離
 30 int vis[V];            //記錄是否被收錄,用來代替集合S
 31 int head[V];          //head[i]表示頂點i的第一條邊的數組下標,"-1"表示頂點i沒有邊
 32 Edge edge[E];
 33 
 34 inline void AddEdge(int a, int b, int w, int id)
 35 {
 36     edge[id].to = b;
 37     edge[id].w = w;
 38     edge[id].next = head[a];  
 39     head[a] = id;             
 40     return;
 41 }
 42 
 43 //s爲起點
 44 void Dijsktra(int s)
 45 {
 46     priority_queue<Node>q;            //取出集合T中的最小值
 47     memset(vis, 0, sizeof(vis));
 48     memset(dis, INF, sizeof(dis));    
 49 
 50     dis[s] = 0;
 51     q.push(Node{ s, dis[s] });
 52     while (!q.empty())
 53     {
 54         Node x = q.top(); q.pop();
 55         int u = x.u;
 56 
 57         if (vis[u])    continue;
 58 
 59         vis[u] = true;
 60         for (int i = head[u]; i != -1; i = edge[i].next)    //鬆弛與u直接相鄰的頂點
 61         {
 62             int v = edge[i].to;
 63             int w = edge[i].w;
 64             if (!vis[v] && dis[u] + w < dis[v])
 65             {
 66                 dis[v] = dis[u] + w;
 67                 q.push(Node{ v,dis[v] });
 68             }
 69         }
 70     }
 71 }
 72 
 73 const int score[] = { 1,2,5,10 };   //每個人單獨行動的時間
 74 
 75 int main()
 76 {
 77     //建圖
 78     memset(head, -1, sizeof(head));
 79     int id = 0;
 80     for (int i = 0; i < (1 << 4); i++)
 81     {
 82         int bits[4];
 83         for (int j = 0; j < 4; j++)  bits[j] = (i >> j) & 1;
 84         //一次走一個人
 85         for (int j = 0; j < 4; j++)  if (bits[j])
 86         {
 87             int tmp = i - (1 << j) + 16;
 88             int w = score[j];
 89             AddEdge(i, tmp, w, id++);
 90             AddEdge(tmp, i, w, id++);
 91         }
 92         //一次走兩個人
 93         for(int j = 0;j < 3;j++)
 94             for (int k = j + 1; k < 4; k++)   if (bits[j] && bits[k])
 95             {
 96                 int tmp = i - (1 << j) - (1 << k) + 16;
 97                 int w = max(score[j],score[k]);
 98                 AddEdge(i, tmp, w, id++);
 99                 AddEdge(tmp, i, w, id++);
100             }
101     }
102     Dijsktra(15);
103     printf("%d\n", dis[16]);
104 
105     return 0;
106 }

複製代碼

此類問題很多,但大多可用圖論的思想做(雖然不一定是速度最快的),後續在補充吧,有問題直接留言!

 

過河問題(二)

問題描述

POJ 1700

在一個漆黑的夜裏,N爲旅行者來到狹窄而沒有護欄的橋邊。如果不借助手電筒的話,大家是無論如何也不敢過橋去的。不幸的是,N個人一共只帶了一隻手電筒,而橋窄得只夠讓兩個人同時過。如果各自單獨過橋的話,N人所需要的時間已知;而如果兩人同時過橋,所需要的時間就是走得比較慢的那個人單獨行動時所需的時間。問題是,如何設計一個方案,讓這N人儘快過橋。

解題思路

很容易想到使用貪心策略,但具體怎麼做呢?

假設共5個人T1、T2、T3、T4、T5,時間一次增加,如何用最少的時間使得5人都過河?

考慮先將T4和T5過河(最慢的兩個安排好了,無後效性),經過簡單比較,有兩種較優方法。

1、T1和T2過河,T1回來,T4和T5過河,T2回來(總時間:T2 + T1 + T5 + T2)

2、T1和T4過河,T1回來,T1和T5過河,T1回來(總時間:T4 + T1 + T5 + T1)

然後問題簡化爲只有3個人T1、T2、T3,n≤3的情況直接觀察。

推廣到n個人(n ≥ 4),只需取(T1 + 2*T2 + Tn)和(2*T1 + Tn-1 + Tn)中的較小者,問題化爲前n-2個人的情況。

代碼實現

複製代碼

 1 #include<stdio.h>
 2 #include<iostream>
 3 #include<algorithm>
 4 using namespace std;
 5 
 6 const int maxn = 1000 + 10;
 7 int n,times[maxn];
 8 
 9 int main()
10 {
11     int T;
12     scanf("%d", &T);
13     while (T--)
14     {
15         scanf("%d", &n);
16         for (int i = 0; i < n; i++)  scanf("%d", &times[i]);
17         sort(times, times + n);
18         int sum = 0;
19         while (n > 3)
20         {
21             sum += min((times[0] + times[1] * 2 + times[n - 1]), (times[0] * 2 + times[n - 2] + times[n - 1]));
22             n -= 2;
23         }
24         if (n == 3)  sum += (times[0] + times[1] + times[2]);
25         if (n == 2)  sum += times[1];
26         if (n == 1)  sum += times[0];
27 
28         printf("%d\n", sum);
29     }
30     return 0;
31 }

複製代碼

 參考鏈接:

https://www.cnblogs.com/ShiChaoPeng/p/5858690.html

https://blog.csdn.net/qq_40788630/article/details/79332774?utm_source=blogxgwz1

參考鏈接:中國大學mooc 劉鐸老師  離散數學

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章