28.6 แบบฝึกหัด
เขียนแต่ละข้อเป็นไฟล์ใหม่ใน Code::Blocks รันแล้วเทียบกับผลลัพธ์ที่ต้องได้ให้ตรงทุกตัวอักษร รวมถึงช่องว่างและการขึ้นบรรทัด ติดจริงๆ ค่อยเปิดคำใบ้ และเปิดเฉลยหลังทำเสร็จแล้วเท่านั้น
ข้อ 1 ทางไปคลินิก
หมู่บ้านแห่งหนึ่งมีจุดสำคัญ N จุด หมายเลข 1 ถึง N และมีถนน M เส้น ถนนแต่ละเส้นเชื่อมสองจุด วิ่งได้ทั้งสองทาง และใช้เวลาเดินทาง w วินาที บ้านของน้องอยู่ที่จุด 1 คลินิกอยู่ที่จุด N ให้หาว่าเดินทางจากบ้านไปคลินิกใช้เวลาน้อยที่สุดกี่วินาที ถ้าไม่มีทางไปถึงคลินิกเลยให้ตอบ -1
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N และ M คั่นด้วยช่องว่าง โดยที่ 2 ≤ N ≤ 100000 และ 1 ≤ M ≤ 200000
อีก M บรรทัด บรรทัดละสามจำนวน u v w หมายถึงถนนระหว่างจุด u กับจุด v ใช้เวลา w วินาที โดยที่ 1 ≤ u, v ≤ N, u ≠ v และ 1 ≤ w ≤ 109 จุดคู่เดียวกันอาจมีถนนเชื่อมมากกว่าหนึ่งเส้น
ข้อมูลออก
เวลาที่น้อยที่สุดเป็นวินาที หรือ -1 ถ้าไปไม่ถึง
6 9
1 2 4
1 3 1
3 2 2
2 4 5
3 4 8
3 5 10
4 5 2
4 6 6
5 6 3135 3
1 2 7
2 3 4
4 5 1-1ดูคำใบ้
ถนนมีเวลาไม่เท่ากัน BFS จึงใช้ไม่ได้ ใช้ Dijkstra แบบ priority_queue ในหน้า 28.2 N ถึงแสน แบบอาเรย์ในหน้า 28.1 ไม่ทัน เวลารวมยาวได้ถึงราว 1014 วินาที ต้องเก็บระยะเป็น long long และใช้อนันต์ที่บวกแล้วไม่ล้น ถ้าจบแล้วระยะของจุด N ยังเป็นอนันต์ ให้ตอบ -1 ถนนซ้ำคู่ไม่ต้องทำอะไรพิเศษ Dijkstra ผ่อนทุกเส้นอยู่แล้ว
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
const long long INF = 1e18;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
vector<vector<pair<int, int>>> adj(n + 1);
for (int i = 0; i < m; i++) {
int u, v, w;
cin >> u >> v >> w;
adj[u].push_back({v, w});
adj[v].push_back({u, w});
}
vector<long long> dist(n + 1, INF);
priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> pq;
dist[1] = 0;
pq.push({0, 1});
while (!pq.empty()) {
auto [d, u] = pq.top();
pq.pop();
if (d > dist[u]) {
continue;
}
for (auto [v, w] : adj[u]) {
if (d + w < dist[v]) {
dist[v] = d + w;
pq.push({dist[v], v});
}
}
}
cout << (dist[n] == INF ? -1 : dist[n]) << '\n';
return 0;
}ข้อ 2 เวลาส่งพัสดุ
เมืองแห่งหนึ่งมีทางแยก N แห่ง หมายเลข 1 ถึง N และมีถนนเดินรถทางเดียว M เส้น ถนนเส้นที่ i วิ่งจากทางแยก u ไปทางแยก v ได้ทางเดียว ใช้เวลา w วินาที ศูนย์กระจายพัสดุตั้งอยู่ที่ทางแยก S รถส่งของออกจากศูนย์ และต้องรู้ว่าไปถึงทางแยกแต่ละแห่งได้เร็วที่สุดภายในกี่วินาที เพื่อใช้ประกาศเวลาส่งให้ลูกค้า
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N M และ S คั่นด้วยช่องว่าง โดยที่ 2 ≤ N ≤ 100000, 1 ≤ M ≤ 200000 และ 1 ≤ S ≤ N
อีก M บรรทัด บรรทัดละสามจำนวน u v w หมายถึงถนนจากทางแยก u ไปทางแยก v ใช้เวลา w วินาที โดยที่ 1 ≤ u, v ≤ N, u ≠ v และ 1 ≤ w ≤ 109
ข้อมูลออก
บรรทัดเดียว มีจำนวนเต็ม N ตัวคั่นด้วยช่องว่าง ตัวที่ i คือเวลาที่น้อยที่สุดจากทางแยก S ไปทางแยก i ถ้าไปไม่ถึงให้พิมพ์ -1 เวลาของทางแยก S เองคือ 0
5 6 2
2 1 4
2 3 1
3 1 2
1 4 7
4 2 3
5 4 13 0 1 10 -1ดูคำใบ้
ถนนเป็นทางเดียว ใส่เฉพาะ adj[u] แล้วรัน Dijkstra แบบ priority_queue จากจุด S ครั้งเดียว ได้ระยะของทุกจุดพร้อมกัน ระยะเกิน int ได้ จุดที่ระยะยังเป็นอนันต์ให้พิมพ์ -1 พิมพ์คั่นด้วยช่องว่างหนึ่งช่อง ไม่มีช่องว่างท้ายบรรทัด
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
const long long INF = 1e18;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, start;
cin >> n >> m >> start;
vector<vector<pair<int, int>>> adj(n + 1);
for (int i = 0; i < m; i++) {
int u, v, w;
cin >> u >> v >> w;
adj[u].push_back({v, w});
}
vector<long long> dist(n + 1, INF);
priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> pq;
dist[start] = 0;
pq.push({0, start});
while (!pq.empty()) {
auto [d, u] = pq.top();
pq.pop();
if (d > dist[u]) {
continue;
}
for (auto [v, w] : adj[u]) {
if (d + w < dist[v]) {
dist[v] = d + w;
pq.push({dist[v], v});
}
}
}
for (int u = 1; u <= n; u++) {
cout << (dist[u] == INF ? -1 : dist[u]) << (u == n ? '\n' : ' ');
}
return 0;
}ข้อ 3 เส้นทางรถนำเที่ยว
บริษัทนำเที่ยวกำลังวางเส้นทางรถบัสจากสนามบินไปโรงแรม จังหวัดนี้มีจุดแวะ N จุด หมายเลข 1 ถึง N สนามบินคือจุด 1 โรงแรมคือจุด N มีถนน M เส้น แต่ละเส้นเชื่อมสองจุด วิ่งได้ทั้งสองทาง ใช้เวลา w วินาที บริษัทต้องการเส้นทางที่ใช้เวลาน้อยที่สุด และต้องพิมพ์ลำดับจุดที่รถผ่านลงในตั๋วด้วย
รับประกันว่าถ้าไปถึงโรงแรมได้ เส้นทางที่ใช้เวลาน้อยที่สุดมีเพียงเส้นเดียว
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N และ M คั่นด้วยช่องว่าง โดยที่ 2 ≤ N ≤ 100000 และ 1 ≤ M ≤ 200000
อีก M บรรทัด บรรทัดละสามจำนวน u v w หมายถึงถนนระหว่างจุด u กับจุด v ใช้เวลา w วินาที โดยที่ 1 ≤ u, v ≤ N, u ≠ v และ 1 ≤ w ≤ 109
ข้อมูลออก
ถ้าไปไม่ถึงโรงแรม ให้พิมพ์ -1 บรรทัดเดียว
ถ้าไปถึง บรรทัดแรกคือเวลาที่น้อยที่สุด บรรทัดที่สองคือหมายเลขจุดที่รถผ่านตามลำดับ ตั้งแต่ 1 จนถึง N คั่นด้วยช่องว่าง
6 9
1 2 4
1 3 1
3 2 2
2 4 5
3 4 8
3 5 10
4 5 2
4 6 6
5 6 313
1 3 2 4 5 64 2
1 2 5
3 4 5-1ดูคำใบ้
Dijkstra แบบ priority_queue พร้อมจด parent[v] = u ทุกครั้งที่ผ่อนสำเร็จ แบบหน้า 28.3 จบแล้วเดินย้อนจากจุด N ตาม parent จนถึงจุด 1 แล้ว reverse ก่อนพิมพ์ ระยะรวมเกิน int ได้ และถ้าไปไม่ถึงให้พิมพ์ -1 บรรทัดเดียว ไม่ต้องพิมพ์บรรทัดที่สอง
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
const long long INF = 1e18;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
vector<vector<pair<int, int>>> adj(n + 1);
for (int i = 0; i < m; i++) {
int u, v, w;
cin >> u >> v >> w;
adj[u].push_back({v, w});
adj[v].push_back({u, w});
}
vector<long long> dist(n + 1, INF);
vector<int> parent(n + 1, 0);
priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> pq;
dist[1] = 0;
pq.push({0, 1});
while (!pq.empty()) {
auto [d, u] = pq.top();
pq.pop();
if (d > dist[u]) {
continue;
}
for (auto [v, w] : adj[u]) {
if (d + w < dist[v]) {
dist[v] = d + w;
parent[v] = u;
pq.push({dist[v], v});
}
}
}
if (dist[n] == INF) {
cout << -1 << '\n';
return 0;
}
vector<int> path;
for (int u = n; u != 0; u = parent[u]) {
path.push_back(u);
}
reverse(path.begin(), path.end());
cout << dist[n] << '\n';
for (int i = 0; i < (int)path.size(); i++) {
cout << path[i] << (i + 1 == (int)path.size() ? '\n' : ' ');
}
return 0;
}ข้อ 4 ศูนย์พักพิงที่ใกล้ที่สุด
อำเภอหนึ่งมีหมู่บ้าน N แห่ง หมายเลข 1 ถึง N และมีถนน M เส้น แต่ละเส้นเชื่อมสองหมู่บ้าน วิ่งได้ทั้งสองทาง ใช้เวลา w วินาที ในอำเภอมีศูนย์พักพิงตั้งอยู่ในหมู่บ้าน K แห่ง เมื่อน้ำท่วม ทุกคนจะอพยพไปศูนย์ที่ไปถึงได้เร็วที่สุดจากหมู่บ้านของตัวเอง
ทางอำเภอต้องการรู้สองอย่าง อย่างแรกคือในบรรดาหมู่บ้านที่ไปถึงศูนย์ใดศูนย์หนึ่งได้ หมู่บ้านที่ต้องใช้เวลาอพยพนานที่สุดใช้เวลากี่วินาที อย่างที่สองคือมีกี่หมู่บ้านที่ไปไม่ถึงศูนย์ไหนเลย หมู่บ้านที่มีศูนย์อยู่แล้วใช้เวลาอพยพ 0 วินาที
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N M และ K คั่นด้วยช่องว่าง โดยที่ 1 ≤ N ≤ 100000, 0 ≤ M ≤ 200000 และ 1 ≤ K ≤ N
อีก M บรรทัด บรรทัดละสามจำนวน u v w หมายถึงถนนระหว่างหมู่บ้าน u กับหมู่บ้าน v ใช้เวลา w วินาที โดยที่ 1 ≤ u, v ≤ N, u ≠ v และ 1 ≤ w ≤ 109
บรรทัดสุดท้ายมีจำนวนเต็ม K ตัว คือหมายเลขหมู่บ้านที่มีศูนย์พักพิง หมายเลขอาจซ้ำกันได้
ข้อมูลออก
บรรทัดเดียว มีจำนวนเต็มสองตัวคั่นด้วยช่องว่าง ตัวแรกคือเวลาอพยพที่นานที่สุดในบรรดาหมู่บ้านที่ไปถึงศูนย์ได้ ตัวที่สองคือจำนวนหมู่บ้านที่ไปไม่ถึงศูนย์ไหนเลย
8 9 2
1 2 7
2 3 4
3 4 6
4 5 3
5 6 5
6 7 2
7 8 9
2 7 15
3 6 8
2 611 06 3 2
1 2 5
2 3 5
4 5 1
1 110 3ดูคำใบ้
รัน Dijkstra จากศูนย์ทีละแห่งแล้วเก็บค่าน้อยสุด ใช้เวลา K เท่าของ Dijkstra หนึ่งครั้ง ซึ่งไม่ทันเมื่อ K เป็นพัน ให้ใส่ทุกศูนย์ลงคิวด้วยระยะ 0 ตั้งแต่แรก แบบหลายจุดเริ่มในหน้า 28.3 แล้วรันครั้งเดียว จบแล้วไล่ดูทุกหมู่บ้าน หาค่ามากสุดในบรรดาที่ไม่ใช่อนันต์ และนับตัวที่เป็นอนันต์ ระยะเกิน int ได้ และหมายเลขศูนย์อาจซ้ำ
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
const long long INF = 1e18;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, k;
cin >> n >> m >> k;
vector<vector<pair<int, int>>> adj(n + 1);
for (int i = 0; i < m; i++) {
int u, v, w;
cin >> u >> v >> w;
adj[u].push_back({v, w});
adj[v].push_back({u, w});
}
vector<long long> dist(n + 1, INF);
priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> pq;
for (int i = 0; i < k; i++) {
int shelter;
cin >> shelter;
if (dist[shelter] != 0) {
dist[shelter] = 0;
pq.push({0, shelter});
}
}
while (!pq.empty()) {
auto [d, u] = pq.top();
pq.pop();
if (d > dist[u]) {
continue;
}
for (auto [v, w] : adj[u]) {
if (d + w < dist[v]) {
dist[v] = d + w;
pq.push({dist[v], v});
}
}
}
long long longest = 0;
int stranded = 0;
for (int u = 1; u <= n; u++) {
if (dist[u] == INF) {
stranded++;
} else {
longest = max(longest, dist[u]);
}
}
cout << longest << ' ' << stranded << '\n';
return 0;
}ข้อ 5 ลุยทุ่งโคลน
ทุ่งนาหลังฝนตกแบ่งเป็นตาราง R แถว C คอลัมน์ ช่องที่ยังเดินได้มีตัวเลขบอกแรงที่ต้องใช้เมื่อก้าวเข้าไปในช่องนั้น ยิ่งโคลนลึกยิ่งใช้แรงมาก ส่วนช่องที่เป็นบ่อน้ำเดินเข้าไปไม่ได้ ชาวนาอยู่ที่ช่องซ้ายบน ต้องไปที่ช่องขวาล่าง ก้าวได้ทีละช่องไปทางบน ล่าง ซ้าย หรือขวา แรงที่ใช้ทั้งหมดคือผลรวมของตัวเลขในทุกช่องที่ก้าวเข้าไป รวมช่องซ้ายบนที่ยืนอยู่ตอนเริ่มด้วย ถ้าเดินผ่านช่องเดิมซ้ำก็ต้องใช้แรงซ้ำ ให้หาแรงรวมที่น้อยที่สุด
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม R และ C คั่นด้วยช่องว่าง โดยที่ 1 ≤ R, C ≤ 500
อีก R บรรทัด บรรทัดละ C จำนวน คั่นด้วยช่องว่าง แต่ละจำนวนเป็น -1 ถ้าช่องนั้นเป็นบ่อน้ำ หรือเป็นจำนวนเต็มตั้งแต่ 1 ถึง 109 ซึ่งคือแรงที่ใช้เมื่อก้าวเข้าช่องนั้น
ข้อมูลออก
แรงรวมที่น้อยที่สุด ถ้าไปถึงช่องขวาล่างไม่ได้ หรือช่องซ้ายบนหรือขวาล่างเป็นบ่อน้ำ ให้พิมพ์ -1 ถ้าตารางมีช่องเดียว คำตอบคือตัวเลขของช่องนั้น
4 6
1 3 9 1 1 2
1 -1 9 -1 9 1
1 1 2 1 1 -1
9 -1 1 1 1 1103 3
1 1 -1
1 -1 1
-1 1 1-1ดูคำใบ้
ให้ช่อง (r, c) เป็นจุดหมายเลข r * C + c แล้วใช้ Dijkstra แบบตารางในหน้า 28.3 ระยะเริ่มของช่องแรกคือค่าของช่องแรก ไม่ใช่ 0 และน้ำหนักของการก้าวคือค่าของช่องที่ก้าวเข้าไป ข้ามช่องที่เป็น -1 ถ้าช่องเริ่มหรือช่องปลายเป็น -1 ตอบ -1 ได้ทันที ผลรวมเกิน int ได้ และตารางมีถึงสองแสนห้าหมื่นช่อง Dijkstra แบบอาเรย์ไม่ทัน
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
const long long INF = 1e18;
int dr[4] = {-1, 1, 0, 0};
int dc[4] = {0, 0, -1, 1};
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int rows, cols;
cin >> rows >> cols;
vector<int> cost(rows * cols);
for (int i = 0; i < rows * cols; i++) {
cin >> cost[i];
}
int last = rows * cols - 1;
if (cost[0] == -1 || cost[last] == -1) {
cout << -1 << '\n';
return 0;
}
vector<long long> dist(rows * cols, INF);
priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> pq;
dist[0] = cost[0];
pq.push({dist[0], 0});
while (!pq.empty()) {
auto [d, id] = pq.top();
pq.pop();
if (d > dist[id]) {
continue;
}
int r = id / cols, c = id % cols;
for (int k = 0; k < 4; k++) {
int nr = r + dr[k], nc = c + dc[k];
if (nr < 0 || nr >= rows || nc < 0 || nc >= cols) {
continue;
}
int next = nr * cols + nc;
if (cost[next] == -1) {
continue;
}
if (d + cost[next] < dist[next]) {
dist[next] = d + cost[next];
pq.push({dist[next], next});
}
}
}
cout << (dist[last] == INF ? -1 : dist[last]) << '\n';
return 0;
}ข้อ 6 นับเส้นทางที่เร็วที่สุด
แอปเรียกรถแห่งหนึ่งอยากรู้ว่าเส้นทางที่เร็วที่สุดจากจุดรับไปจุดส่งมีกี่แบบ เพื่อใช้กระจายรถไม่ให้กระจุกบนถนนเส้นเดียว แผนที่มีทางแยก N แห่ง หมายเลข 1 ถึง N และถนน M เส้น แต่ละเส้นเชื่อมสองทางแยก วิ่งได้ทั้งสองทาง ใช้เวลา w วินาที จุดรับคือทางแยก 1 จุดส่งคือทางแยก N
เส้นทางสองเส้นถือว่าต่างกันถ้าลำดับของถนนที่ใช้ต่างกัน ถ้ามีถนนสองเส้นเชื่อมทางแยกคู่เดียวกัน การใช้ถนนคนละเส้นก็นับเป็นคนละเส้นทาง ให้หาเวลาที่น้อยที่สุด และจำนวนเส้นทางที่ใช้เวลาน้อยที่สุดนั้น จำนวนอาจมากมาก จึงให้ตอบเศษจากการหารด้วย 1000000007
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N และ M คั่นด้วยช่องว่าง โดยที่ 2 ≤ N ≤ 100000 และ 1 ≤ M ≤ 200000
อีก M บรรทัด บรรทัดละสามจำนวน u v w หมายถึงถนนระหว่างทางแยก u กับทางแยก v ใช้เวลา w วินาที โดยที่ 1 ≤ u, v ≤ N, u ≠ v และ 1 ≤ w ≤ 109
ข้อมูลออก
ถ้าไปไม่ถึงทางแยก N ให้พิมพ์ -1 ถ้าไปถึง ให้พิมพ์จำนวนเต็มสองตัวคั่นด้วยช่องว่าง คือเวลาที่น้อยที่สุด และจำนวนเส้นทางที่ใช้เวลาเท่านั้น mod 1000000007
6 9
1 2 2
1 3 2
2 3 1
2 4 3
3 4 3
1 4 7
4 5 1
4 6 4
5 6 39 45 4
1 2 3
2 3 3
3 1 3
4 5 1-1ดูคำใบ้
Dijkstra แบบ priority_queue พร้อม ways แบบหน้า 28.3 ได้ระยะใหม่ที่น้อยกว่าเดิม ตั้ง ways[v] = ways[u] ได้ระยะเท่าเดิม บวก ways[u] แล้ว mod 1000000007 ต้องข้ามคู่เก่าเสมอ ไม่งั้นจุดที่ถูกหยิบซ้ำจะบวกจำนวนทางซ้ำ ระยะเกิน int ได้ และถนนซ้ำคู่ที่ยาวเท่ากันนับเป็นคนละทาง
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
const long long INF = 1e18;
const long long MOD = 1000000007;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
vector<vector<pair<int, int>>> adj(n + 1);
for (int i = 0; i < m; i++) {
int u, v, w;
cin >> u >> v >> w;
adj[u].push_back({v, w});
adj[v].push_back({u, w});
}
vector<long long> dist(n + 1, INF), ways(n + 1, 0);
priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> pq;
dist[1] = 0;
ways[1] = 1;
pq.push({0, 1});
while (!pq.empty()) {
auto [d, u] = pq.top();
pq.pop();
if (d > dist[u]) {
continue;
}
for (auto [v, w] : adj[u]) {
if (d + w < dist[v]) {
dist[v] = d + w;
ways[v] = ways[u];
pq.push({dist[v], v});
} else if (d + w == dist[v]) {
ways[v] = (ways[v] + ways[u]) % MOD;
}
}
}
if (dist[n] == INF) {
cout << -1 << '\n';
} else {
cout << dist[n] << ' ' << ways[n] << '\n';
}
return 0;
}ข้อ 7 ไปส่งแล้ววนกลับ
ร้านน้ำดื่มตั้งอยู่ที่ทางแยก 1 ในเมืองที่มีทางแยก N แห่ง หมายเลข 1 ถึง N และถนนเดินรถทางเดียว M เส้น ถนนแต่ละเส้นวิ่งจากทางแยก u ไปทางแยก v ได้ทางเดียว ใช้เวลา w วินาที ทุกทางแยกตั้งแต่ 2 ถึง N มีลูกค้าหนึ่งบ้าน วันนี้ต้องส่งน้ำให้ทุกบ้าน รถออกจากร้านไปส่งทีละบ้าน ส่งเสร็จแล้วต้องกลับมาที่ร้านก่อนออกไปบ้านถัดไป และทุกเที่ยววิ่งทางที่เร็วที่สุดทั้งขาไปและขากลับ
ให้หาเวลารวมของทุกเที่ยว คือผลรวมของ (เวลาที่น้อยที่สุดจากร้านไปบ้าน + เวลาที่น้อยที่สุดจากบ้านกลับร้าน) ของทุกบ้าน ถ้ามีบ้านใดที่รถไปไม่ถึงหรือกลับมาไม่ได้ ให้ตอบ -1
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N และ M คั่นด้วยช่องว่าง โดยที่ 2 ≤ N ≤ 100000 และ 1 ≤ M ≤ 200000
อีก M บรรทัด บรรทัดละสามจำนวน u v w หมายถึงถนนจากทางแยก u ไปทางแยก v ใช้เวลา w วินาที โดยที่ 1 ≤ u, v ≤ N, u ≠ v และ 1 ≤ w ≤ 106
ข้อมูลออก
เวลารวมของทุกเที่ยว หรือ -1
4 6
1 2 5
2 3 2
3 1 4
1 3 10
3 4 1
4 1 3333 3
1 2 4
2 1 4
1 3 1-1ดูคำใบ้
ขาไปคือระยะจากจุด 1 ไปทุกบ้าน ได้จาก Dijkstra ครั้งเดียว ขากลับคือระยะจากทุกบ้านมาจุด 1 ซึ่งถ้ารัน Dijkstra จากทุกบ้านจะช้า N เท่า ให้สร้างกราฟอีกชุดที่กลับทิศถนนทุกเส้น ระยะจาก v มา 1 ในกราฟเดิมเท่ากับระยะจาก 1 ไป v ในกราฟกลับทิศ จึงรัน Dijkstra จากจุด 1 บนกราฟกลับทิศอีกครั้งเดียวก็ได้ขากลับของทุกบ้าน ผลรวมเกิน int และถ้าบ้านไหนไปไม่ถึงหรือกลับไม่ได้ ให้ตอบ -1
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
const long long INF = 1e18;
vector<long long> dijkstra(const vector<vector<pair<int, int>>> &adj, int start)
{
int n = (int)adj.size() - 1;
vector<long long> dist(n + 1, INF);
priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> pq;
dist[start] = 0;
pq.push({0, start});
while (!pq.empty()) {
auto [d, u] = pq.top();
pq.pop();
if (d > dist[u]) {
continue;
}
for (auto [v, w] : adj[u]) {
if (d + w < dist[v]) {
dist[v] = d + w;
pq.push({dist[v], v});
}
}
}
return dist;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
vector<vector<pair<int, int>>> forward(n + 1), backward(n + 1);
for (int i = 0; i < m; i++) {
int u, v, w;
cin >> u >> v >> w;
forward[u].push_back({v, w});
backward[v].push_back({u, w});
}
vector<long long> go = dijkstra(forward, 1);
vector<long long> back = dijkstra(backward, 1);
long long total = 0;
for (int v = 2; v <= n; v++) {
if (go[v] == INF || back[v] == INF) {
cout << -1 << '\n';
return 0;
}
total += go[v] + back[v];
}
cout << total << '\n';
return 0;
}ข้อ 8 รถขนสินค้าขากลับ
บริษัทรถบรรทุกมีโกดัง N แห่ง หมายเลข 1 ถึง N และเส้นทางวิ่ง M เส้น เส้นทางแต่ละเส้นวิ่งจากโกดัง u ไปโกดัง v ได้ทางเดียว มีค่าใช้จ่าย w บาท ถ้าวิ่งเส้นนั้นแล้วได้ค่าจ้างขนสินค้ามากกว่าค่าน้ำมัน ค่าใช้จ่ายจะติดลบ วิ่งเส้นทางเดิมซ้ำกี่ครั้งก็ได้ และทุกครั้งมีค่าใช้จ่ายเท่าเดิม
รถคันหนึ่งออกจากโกดัง 1 และต้องไปจบที่โกดัง N ให้หาค่าใช้จ่ายรวมที่น้อยที่สุด ระหว่างทางจะผ่านโกดังใดซ้ำกี่ครั้งก็ได้ รวมทั้งโกดัง N เองด้วย
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N และ M คั่นด้วยช่องว่าง โดยที่ 2 ≤ N ≤ 2000 และ 1 ≤ M ≤ 5000
อีก M บรรทัด บรรทัดละสามจำนวน u v w หมายถึงเส้นทางจากโกดัง u ไปโกดัง v มีค่าใช้จ่าย w บาท โดยที่ 1 ≤ u, v ≤ N, u ≠ v และ -109 ≤ w ≤ 109
ข้อมูลออก
- ถ้าไม่มีทางไปถึงโกดัง N ให้พิมพ์
unreachable - ถ้าทำให้ค่าใช้จ่ายรวมน้อยลงได้เรื่อยๆ ไม่สิ้นสุด ให้พิมพ์
-inf - นอกจากนั้นให้พิมพ์ค่าใช้จ่ายรวมที่น้อยที่สุด ซึ่งอาจติดลบได้
4 4
2 4 3
3 2 -4
1 3 5
1 2 245 5
1 2 4
2 3 -2
3 4 3
4 2 -2
4 5 1-infดูคำใบ้
มีค่าติดลบ Dijkstra ใช้ไม่ได้ ใช้ Bellman-Ford ในหน้า 28.4 N × M ไม่เกิน 107 ผ่อน V - 1 รอบแล้วตรวจต่ออีก V รอบ จุดที่ยังผ่อนได้และทุกจุดที่เดินต่อจากจุดพวกนั้นได้คือจุดที่ลดได้ไม่สิ้นสุด ถ้าจุด N อยู่ในพวกนี้ตอบ -inf ระวังวงติดลบที่ไปไม่ถึงจุด N หรือที่ไปจากจุด 1 ไม่ถึง สองแบบนี้ไม่มีผลกับคำตอบ ผ่อนเฉพาะถนนที่ต้นทางไปถึงแล้ว และคำตอบต่ำกว่าค่าน้อยสุดของ int ได้
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
const long long INF = 1e18;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
vector<int> from(m), to(m), cost(m);
for (int i = 0; i < m; i++) {
cin >> from[i] >> to[i] >> cost[i];
}
vector<long long> dist(n + 1, INF);
dist[1] = 0;
for (int round = 1; round <= n - 1; round++) {
bool changed = false;
for (int i = 0; i < m; i++) {
int u = from[i], v = to[i];
if (dist[u] != INF && dist[u] + cost[i] < dist[v]) {
dist[v] = dist[u] + cost[i];
changed = true;
}
}
if (!changed) {
break;
}
}
vector<bool> endless(n + 1, false);
for (int round = 1; round <= n; round++) {
for (int i = 0; i < m; i++) {
int u = from[i], v = to[i];
if (dist[u] == INF) {
continue;
}
if (endless[u] || dist[u] + cost[i] < dist[v]) {
endless[v] = true;
dist[v] = min(dist[v], dist[u] + cost[i]);
}
}
}
if (dist[n] == INF) {
cout << "unreachable" << '\n';
} else if (endless[n]) {
cout << "-inf" << '\n';
} else {
cout << dist[n] << '\n';
}
return 0;
}ข้อ 9 เมืองนัดพบ
เพื่อนร่วมรุ่น K คนนัดเจอกันช่วงปีใหม่ ประเทศนี้มีเมือง N เมือง หมายเลข 1 ถึง N และมีถนน M เส้น แต่ละเส้นเชื่อมสองเมือง วิ่งได้ทั้งสองทาง ใช้เวลา w นาที เพื่อนๆ ต้องเลือกเมืองนัดพบหนึ่งเมือง ซึ่งจะเป็นเมืองที่มีเพื่อนอยู่หรือไม่ก็ได้ ทุกคนเดินทางจากเมืองของตัวเองมาเมืองนัดพบด้วยทางที่เร็วที่สุด
ให้เลือกเมืองนัดพบที่ทำให้ผลรวมเวลาเดินทางของทุกคนน้อยที่สุด เมืองนัดพบต้องเป็นเมืองที่เพื่อนทุกคนเดินทางมาถึงได้ ถ้ามีหลายเมืองที่ผลรวมน้อยที่สุดเท่ากัน ให้เลือกเมืองที่หมายเลขน้อยที่สุด
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N M และ K คั่นด้วยช่องว่าง โดยที่ 1 ≤ N ≤ 300, 0 ≤ M ≤ 50000 และ 1 ≤ K ≤ 1000
อีก M บรรทัด บรรทัดละสามจำนวน u v w หมายถึงถนนระหว่างเมือง u กับเมือง v ใช้เวลา w นาที โดยที่ 1 ≤ u, v ≤ N, u ≠ v และ 1 ≤ w ≤ 109 เมืองคู่เดียวกันอาจมีถนนเชื่อมมากกว่าหนึ่งเส้น
บรรทัดสุดท้ายมีจำนวนเต็ม K ตัว คือเมืองที่เพื่อนแต่ละคนอยู่ เพื่อนหลายคนอาจอยู่เมืองเดียวกัน
ข้อมูลออก
ถ้าไม่มีเมืองไหนที่เพื่อนทุกคนมาถึงได้ ให้พิมพ์ -1 นอกนั้นให้พิมพ์จำนวนเต็มสองตัวคั่นด้วยช่องว่าง คือหมายเลขเมืองนัดพบ และผลรวมเวลาเดินทางของทุกคน
5 6 3
1 2 4
2 3 3
3 4 2
4 5 6
1 5 20
2 4 9
1 3 53 154 2 2
1 2 5
3 4 5
1 4-1ดูคำใบ้
N ไม่เกิน 300 ใช้ Floyd-Warshall ในหน้า 28.5 หาระยะทุกคู่ได้ใน 2.7 × 107 ขั้น แล้วลองทุกเมืองเป็นเมืองนัดพบ รวมระยะจากเมืองของเพื่อนทุกคน ข้ามเมืองที่มีเพื่อนคนใดไปไม่ถึง เริ่มตารางด้วยถนนที่สั้นที่สุดของแต่ละคู่ เพราะคู่เดียวกันอาจมีถนนหลายเส้น ลูป k ต้องอยู่นอกสุด ผลรวมเกิน int และอนันต์ต้องบวกกันแล้วไม่ล้น
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
const long long INF = 1e18;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, k;
cin >> n >> m >> k;
vector<vector<long long>> d(n + 1, vector<long long>(n + 1, INF));
for (int i = 1; i <= n; i++) {
d[i][i] = 0;
}
for (int e = 0; e < m; e++) {
int u, v, w;
cin >> u >> v >> w;
d[u][v] = min(d[u][v], (long long)w);
d[v][u] = min(d[v][u], (long long)w);
}
for (int via = 1; via <= n; via++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (d[i][via] + d[via][j] < d[i][j]) {
d[i][j] = d[i][via] + d[via][j];
}
}
}
}
vector<int> home(k);
for (int i = 0; i < k; i++) {
cin >> home[i];
}
int best_town = -1;
long long best_total = INF;
for (int town = 1; town <= n; town++) {
long long total = 0;
bool everyone = true;
for (int i = 0; i < k; i++) {
if (d[home[i]][town] == INF) {
everyone = false;
break;
}
total += d[home[i]][town];
}
if (everyone && total < best_total) {
best_total = total;
best_town = town;
}
}
if (best_town == -1) {
cout << -1 << '\n';
} else {
cout << best_town << ' ' << best_total << '\n';
}
return 0;
}ข้อ 10 ตั๋วผ่านทางฟรีหนึ่งใบ
น้องได้รางวัลเป็นตั๋วผ่านทางฟรีหนึ่งใบ ใช้ได้กับถนนหนึ่งเส้นเท่านั้น ถนนเส้นที่ใช้ตั๋วจะไม่เสียค่าผ่านทางเลย ประเทศนี้มีเมือง N เมือง หมายเลข 1 ถึง N และมีถนน M เส้น แต่ละเส้นเชื่อมสองเมือง วิ่งได้ทั้งสองทาง ค่าผ่านทาง w บาท น้องอยู่เมือง 1 และจะไปเมือง N โดยจะใช้ตั๋วกับถนนเส้นใดก็ได้ไม่เกินหนึ่งเส้น ให้หาค่าผ่านทางรวมที่น้อยที่สุด
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N และ M คั่นด้วยช่องว่าง โดยที่ 2 ≤ N ≤ 100000 และ 1 ≤ M ≤ 200000
อีก M บรรทัด บรรทัดละสามจำนวน u v w หมายถึงถนนระหว่างเมือง u กับเมือง v ค่าผ่านทาง w บาท โดยที่ 1 ≤ u, v ≤ N, u ≠ v และ 1 ≤ w ≤ 109
ข้อมูลออก
ค่าผ่านทางรวมที่น้อยที่สุด หรือ -1 ถ้าไปเมือง N ไม่ได้
6 9
1 2 4
1 3 1
3 2 2
2 4 5
3 4 8
3 5 10
4 5 2
4 6 6
5 6 344 2
1 2 5
3 4 5-1ดูคำใบ้
ลองทุกถนนเป็นถนนที่ใช้ตั๋วแล้วรัน Dijkstra ใหม่ทุกครั้ง ช้าไป M เท่า มีสองวิธีที่เร็ว วิธีแรก รัน Dijkstra จากจุด 1 ได้ from1 และจากจุด N ได้ fromN ถ้าใช้ตั๋วกับถนน u v คำตอบคือ from1[u] + fromN[v] ลองทุกถนนทั้งสองทิศแล้วเอาค่าน้อยสุด วิธีที่สอง ให้สถานะเป็น (จุด, ใช้ตั๋วไปแล้วหรือยัง) มี 2N สถานะ จากสถานะที่ยังไม่ใช้ตั๋ว เดินถนนแบบจ่ายเต็มหรือแบบใช้ตั๋วก็ได้ ส่วนสถานะที่ใช้ไปแล้วจ่ายเต็มอย่างเดียว แล้วรัน Dijkstra บนสถานะ ทั้งสองวิธีเป็น O((N + M) log N) ระวังอย่าให้ใช้ตั๋วได้สองครั้ง และระยะเกิน int
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
const long long INF = 1e18;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
vector<vector<pair<int, int>>> adj(n + 1);
for (int i = 0; i < m; i++) {
int u, v, w;
cin >> u >> v >> w;
adj[u].push_back({v, w});
adj[v].push_back({u, w});
}
vector<long long> dist(2 * n + 1, INF);
priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> pq;
dist[1] = 0;
pq.push({0, 1});
while (!pq.empty()) {
auto [d, state] = pq.top();
pq.pop();
if (d > dist[state]) {
continue;
}
bool used = state > n;
int u = used ? state - n : state;
for (auto [v, w] : adj[u]) {
int next = used ? n + v : v;
if (d + w < dist[next]) {
dist[next] = d + w;
pq.push({dist[next], next});
}
if (!used && d < dist[n + v]) {
dist[n + v] = d;
pq.push({d, n + v});
}
}
}
long long answer = min(dist[n], dist[2 * n]);
cout << (answer == INF ? -1 : answer) << '\n';
return 0;
}