ข้อนี้กำหนดให้มีถ้ำที่มี โถงและทางเชื่อมระหว่างโถง ทางเชื่อม โดยโจทย์นี้ต้องให้จำลองหาเวลาที่จะใช้เพื่อไปจากโถง ไปยังโถง ณ เวลาต่างๆ ในตอนแรกระดับน้ำจะอยู่ที่ แต่ระดับน้ำจะเพิ่มขึ้นเรื่อยๆ และทำให้ระยะเวลาการเดินทางในแต่ละทางเชื่อมเพิ่มขึ้น
แต่ละทางเชื่อมจะไปจากโถง ไปยังโถง และใช้เวลา (จะไปจาก ไปยัง เท่านั้น ไม่สามารถไปจาก ไป ด้วยทางเชื่อมเดียวกัน) ในตอนเริ่มคือ เมื่อผ่านไปถึงเวลา จะกลายเป็น ยกเว้นทางเชื่อมที่ติดกับ ซึ่งจะเป็น ตลอด
จากนั้นโจทย์จะถามผลการจำลอง คำถามว่าเวลาที่ใช้เดินทางจาก ไป ที่เป็นไปได้ต่ำสุดคือเท่าไหร่เมื่อระดับน้ำคือ
แนวคิด
ข้อนี้เป็นโจทย์ Dijkstra
อย่างแรกสังเกตว่าเราไม่จำเป็นต้องกลับมายังจุดเริ่มต้นเพราะเพียงแต่จะทำให้การไปถึง ช้าลง ดังนั้นจึงสามารถตัดทางเชื่อมใดๆ ที่มีจบออกมายัง
สมมติว่าเส้นทางเดินที่เลือกคือ ณ ระดับความสูงน้ำ ระยะเวลาการเดินทางรวมคือ ดังนั้นหากทำ Dijkstra สำหรับทุกการจำลอง ครั้งจะได้ทำตอบที่ต้องการ แต่ะจะใช้เวลานานเกินไป ซึ่งช้าเกินไป
สังเกตได้ว่า จะผ่านทางเชื่อม ทางเชื่อมและ ดังนั้นเวลาที่ใช้คือระยะทางที่เวลา บวกกับจำนวนทางเชื่อมที่ผ่านลบ คูณกับ
เราสามารถแปลงกราฟเดิมเป็นกราฟใหม่โดยมองว่าแต่ละจุดยอดใน Graph แทน State ว่าอยู่ที่โถง และผ่านมาแล้ว ทางเชื่อม เราไม่ต้องสนใจ State ที่ผ่านไปเกิน ทางเชื่อมเพราะเส้นทางดังกล่าวจะมี Cycle ที่สามารถตัดออกและลดระยะเวลาได้ ดังนั้นจะมี State คือจบที่โถง ระหว่าง ถึง และผ่านไปแล้ว ทางเชื่อมระหว่าง ถึง ทางเชื่อมในกราฟใหม่จะเพิ่มจากกราฟเก่าที่มี เป็น เพราะจะต้องมีหนึ่งทางเชื่อมสำหรับทุก ตั้งแต่ะ ถึง
การทำ Dijkstra บนกราฟใหม่นี้จะทำให้ได้ แทนผลรวม ในเส้นทางจาก ไป โดยผ่าน ทางเชื่อมที่ต่ำสุดที่เป็นไปได้ โดย Dijkstra จะใช้เวลา
Dijkstra จะทำให้ได้คำตอบว่าหากเริ่มที่ และไปถึง โดยผ่านไปแล้ว ทางเชื่อมจะทำให้ผลรวม เป็นไปได้ต่ำสุดคือเท่าไหร่
เมื่อเรามี สำหรับทุก ตั้งแต่ ถึง แล้วเราจะสามารถหาคำตอบแต่ละ โดยการไล่ หาค่า ที่ต่ำสุดเป็นคำตอบซึ่งจะใช้เวลา ซึ่งพอสำหรับข้อนี้
Dijkstra's Algorithm
Dijkstra's Algorithm เป็นขั้นตอนวิธีที่ใช้หาระยะทางสั้นในกราฟสุดจากจุดยอดเริ่มต้น ไปยังทุกจุดยอด สมมิตว่ากราฟที่พิจารณามี จุดยอดและ ทางเชื่อม
ให้ระยะของเส้นเชื่อมระหว่าง กับ เป็น (สังเกตว่าใน Dijkstra หากมีมากกว่าหนึ่งเส้นเชื่อมระหว่าง กับ จะสามารถเลือกอันที่สั้นสุดมาอันเดียวเราจึงสามารถพิจารณาแค่กรณีที่กราฟเป็นกราฟเชิงเดียว (Simple Graph) ซึ่งแปลว่าจาก ไป มีอย่างมากเส้นเชื่อมเดียว)
หลักการทำงานของ Dijkstra คือจะเก็บระยะ สำหรับแต่ละจุดยอด ในกราฟซึ่งแทนระยะทางต่ำสุดจาก ไปยัง ที่พบแล้ว ในตอนเริ่มต้นจะตั้ง และ สำหรับ จากนั้นในแต่ละขั้นจะเลือกจุดยอด ที่ยังไม่ได้พิจารณาที่มี ต่ำสุด (โดยที่ ไปถึงได้นั่นคือ ) และพิจารณาแต่ละเส้นเชื่อมออกจาก ไปยัง ว่า ต่ำกว่า ในปัจจุบันหรือไม่ หากใช่จะแก้ (เพราะเป็นเส้นทางที่ผ่าน ไปถึง ที่ใช้เวลาดังกล่าว)
ใน Implementation ทั่วไป จะใช้ Binary Heap เพื่อหา ที่มี ต่ำสึดในแต่ละขั้นจนกว่า Heap จะว่าง ซึ่งจะทำให้การแก้ค่า และใส่ใน Heap ใหม่ใช้เวลา และการหาค่าต่ำสุดจะใช้เวลา เช่นกัน (อ่านเรื่อง Binary Heap เพิ่มได้จาก https://programming.in.th/tasks/1021/solution) สำหรับกราฟเชิงเดียวจะได้ว่า เพราะ
แต่ละทางเชื่อมจะถูกพิจารณาอย่างมากครั้งเดียวดังนั้นการใส่ค่าใหม่ใน Binary Heap จะเกิดขึ้นอย่างมาก ครั้ง ซึ่งแปลว่าเวลาที่ใช้กับขั้นตอนวิธีทั้งหมดรวมทั้งการนำเข้าและเอาออกจะเป็น เมื่อรวมกับการตั้งค่าเริ่มต้นของ สำหรับทุก จะได้เป็น สำหรับทั้งขั้นตอนวิธี
โค้ดตัวอย่างสำหรับ Dijkstra ทั่วไป (ดัดแปลงจาก https://usaco.guide/CPH.pdf#page=136)
int dist[MAX]; bool visited[MAX]; vector<pair<int, int>> edges[MAX]; void dijkstra(int N, int S) { for (int i = 1; i <= N; i++) dist[i] = INF; dist[S] = 0; priority_queue<pair<int, int>> q; q.push({0, S}); while (!q.empty()) { int a = q.top().second; q.pop(); if (visited[a]) continue; visited[a] = true; for (auto e : edges[a]) { int b = e.first, w = e.second; if (dist[a] + w < dist[b]) { dist[b] = dist[a] + w; q.push({-dist[b], b}); } } } }
โค้ดนี้เก็บเส้นเชื่อมเป็น edges[a]
สำหรับทางเชื่อมที่ออกจาก ด้วย pair<int,int>
โดยค่าแรกใน pair
จะเป็นอีกปลาย ของแต่เส้นเชื่อม และค่าที่สองจะเป็นระยะของเส้น
ในโค้ดนี้ใช้std::priority_queue
เป็น Heap สังเกตว่าจะใช้ -dist[b]
เป็นค่าแรกเพราะ std::priority_queue
จะเอาค่ามาสุดมาก่อน การใช้ค่าติดลบจึงทำให้เอาค่า dist
ที่ต่ำสุดมาก่อนตามที่ต้องการ
Dijkstra สำหรับข้อนี้
สำหรับข้อนี้จะต้องแปลงให้แต่ละจุดยอดในกราฟเก็บทั้งหมายเลขของโถง และจำนวน เพื่อให้เป็น State ตามที่อธบิายไว้
ดังนั้นจะต้องแก้ให้ dist
และ visited
ให้เป็น Array 2 มิติ และใน priority_queue
จะต้องเป็น State เป็น pair
ของค่าแทนที่จะเป็นค่าเดียว
long long dist[MAX][MAX]; int visited[MAX][MAX]; vector<pair<int, int>> edges[MAX]; void dijkstra(int N, int S) { priority_queue<pair<int, pair<int, int>>> q; for (int i = 0; i <= N; i++) for (int j = 0; j <= N; j++) dist[i][j] = 1000000000000000000LL, visited[i][j] = false; dist[S][0] = 0; q.push({-0, {S, 0}}); while (!q.empty()) { int a = q.top().second.first; int c = q.top().second.second; q.pop(); if (visited[a][c]) continue; visited[a][c] = true; if (c >= N) // ไม่ต้องพิจารณาไปต่อถ้า State ปัจจุบันผ่านมาแล้ว N ทางเชื่อม continue; for (auto e : edges[a]) { int b = e.first, w = e.second; if (dist[b][c + 1] > dist[a][c] + w) { dist[b][c + 1] = dist[a][c] + w; q.push({-dist[b][c + 1], {b, c + 1}}); } } } }