Bin Laden
Đề bài
Trùm khủng bố Bin Laden trốn trong 1 căn hầm được đào sâu xuống mặt đất $M$ tầng, mỗi tầng có $N$ phòng. Các phòng được ngăn cách bằng các cửa rất khó phá. Các phòng có cửa xuống phòng ngay phía dưới và 2 phòng ở 2 bên. Từ trên mặt đất có $N$ cửa xuống $N$ phòng tầng $-1$. Bin Laden ở tầng dưới cùng (tầng $-M$) phòng thứ $N$ (phòng ở bên phải nhất). Mỗi cửa được làm bằng một kim loại khác nhau với độ dày khác nhau nên việc phá cửa cần thời gian khác nhau.
Bạn hãy tìm cách đi từ mặt đất xuống phòng của Bin Laden nhanh nhất không hắn thoát mất.
Input
- Dòng 1 ghi $M$ và $N$.
- Dòng 2 đến $2M + 1$: dòng chẵn ghi $N$ số, dòng lẻ ghi $N - 1$ số là chi phí để phá cửa.
Giới hạn
- $1 \le M \le 2222$
- $1 \le N \le 10$
- Chi phí của các cánh cửa thuộc $[0, 1000]$.
Output: ghi ra 1 số là thời gian nhỏ nhất để đến được phòng của Bin Laden.
4 2
99 10
1
10 99
1
99 10
1
10 99
144Lời mở đầu
Bài này mình được một bạn học viên hỏi nên sẵn tiện mình viết bài giải chi tiết để nhiều bạn được tham khảo luôn. Hy vọng mang đến được giá trị cho các bạn 😇.
Bấm vào nút bên dưới để nộp bài và được hệ thống CLUE OJ chấm tự động.
Nộp bài trên CLUE OJKiến thức cần có: thuật toán Dijkstra
Để giải quyết được bài này đòi hỏi phải biết về thuật toán Dijkstra. Nếu bạn nào chưa biết về nó thì có thể tham khảo tại link này: Đường đi ngắn nhất – VNOI Wiki.
Thì chỉ là dùng Dijkstra cơ bản thôi, nhưng quan trọng là các bạn phải biết từ dữ liệu ban đầu bài cho mà phải mô hình hóa thành dạng đồ thị, rồi từ đó áp dụng Dijkstra trên đồ thị đó.
Mô hình hoá thành đồ thị
Hãy xem hình dưới đây thầy mô hình hóa thành đồ thị nhé, với các số màu đen là các đỉnh của đồ thị và các gạch nối màu đỏ giữa chúng là các cạnh của đồ thị. Và bên cạnh là kết quả đường đi của ví dụ mẫu:
Với ví dụ dữ liệu mẫu đề cho nhé, ta sẽ có 10 đỉnh đánh số từ 1 đến 10 như hình trên, và tụi nó nối với nhau như trên, tức là ta sẽ có danh sách cạnh là (đỉnh – đỉnh : chi phí):
Hai đỉnh 1 và 2 ở trên cùng đại diện cho mặt đất (phía trên 2 cửa đi xuống tầng $-1$), mỗi tầng tiếp theo có $N$ đỉnh ứng với $N$ phòng. Tức là ta có số lượng đỉnh:
Giờ đây bài toán là: cần tìm đường đi từ một trong số các đỉnh $1, 2, 3, \ldots, N$ để đi đến đỉnh $V$ với chi phí ít nhất.
dist[1], dist[2], ..., dist[n], thì đó là đáp án bài toán. Vì cửa đi được cả 2 chiều nên đồ thị là vô hướng, đi từ $V$ ngược lên cũng cho cùng chi phí như đi từ mặt đất xuống, mà chỉ cần chạy Dijkstra đúng 1 lần.Với ví dụ mẫu, đường đi tốt nhất là đi theo đường zigzac: 2 → 4 → 3 → 5 → 6 → 8 → 7 → 9 → 10, tổng chi phí $10 + 1 + 10 + 1 + 10 + 1 + 10 + 1$ $= 44$.
Em nhập số tầng, số phòng và chi phí các cửa rồi bấm Tiếp để xem Dijkstra chạy ngược từ phòng Bin Laden, mỗi bước chốt 1 đỉnh có dist nhỏ nhất:
Cách đọc input để dựng cạnh
Trong code, mình duyệt $i$ từ 1 đến $2M$ (ứng với $2M$ dòng chi phí). Đỉnh bắt đầu của dòng thứ $i$ là u = (i / 2) * n + 1:
- $i$ lẻ (dòng có $N$ số): đây là các cửa đi xuống, nối đỉnh
uvới đỉnhu + nngay bên dưới, lần lượt cho $N$ cột. - $i$ chẵn (dòng có $N - 1$ số): đây là các cửa ngang trong cùng 1 tầng, nối đỉnh
uvới đỉnhu + 1bên phải, lần lượt cho $N - 1$ cặp phòng kề nhau.
Mỗi cạnh được thêm vào danh sách kề của cả 2 đỉnh vì đồ thị vô hướng.
Source code
#include <bits/stdc++.h>
using namespace std;
#define int long long
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int m, n;
cin >> m >> n;
int V = (m + 1) * n;
vector<vector<pair<int, int>>> graph(V + 1, vector<pair<int, int>>());
int sz = 2 * m;
for(int i = 1; i <= sz; ++i){
int u = (i / 2) * n + 1;
int v, w;
if(i % 2 != 0){
for(int j = 1; j <= n; ++j){
cin >> w;
v = u + n;
graph[u].push_back({v, w});
graph[v].push_back({u, w});
u++;
}
}
else{
v = u + 1;
for(int j = 1; j < n; ++j){
cin >> w;
graph[u].push_back({v, w});
graph[v].push_back({u, w});
u++;
v++;
}
}
}
int INF = 1e9;
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq; // cost, u
vector<int> dist(V + 1, INF);
pq.push({0, V});
dist[V] = 0;
while(!pq.empty()){
pair<int, int> top = pq.top();
pq.pop();
int w = top.first;
int u = top.second;
if(dist[u] != w){
continue;
}
for(int i = 0; i < graph[u].size(); ++i){
pair<int, int> neighbor = graph[u][i];
if(w + neighbor.second < dist[neighbor.first]){
dist[neighbor.first] = w + neighbor.second;
pq.push({dist[neighbor.first], neighbor.first});
}
}
}
int res = INF;
for(int i = 1; i <= n; ++i){
res = min(res, dist[i]);
}
cout << res;
return 0;
}
import sys
import heapq
data = list(map(int, sys.stdin.read().strip().split()))
pos = 0
m, n = data[pos], data[pos + 1]
pos += 2
V = (m + 1) * n
graph = [[] for _ in range(V + 1)]
sz = 2 * m
for i in range(1, sz + 1):
u = (i // 2) * n + 1
if i % 2 != 0:
for _ in range(n):
w = data[pos]
pos += 1
v = u + n
graph[u].append((v, w))
graph[v].append((u, w))
u += 1
else:
v = u + 1
for _ in range(1, n):
w = data[pos]
pos += 1
graph[u].append((v, w))
graph[v].append((u, w))
u += 1
v += 1
INF = 10**9
dist = [INF] * (V + 1)
pq = [(0, V)] # (cost, node)
dist[V] = 0
while pq:
w, u = heapq.heappop(pq)
if dist[u] != w:
continue
for v, cost in graph[u]:
if w + cost < dist[v]:
dist[v] = w + cost
heapq.heappush(pq, (dist[v], v))
res = min(dist[1:n + 1])
print(res)
Đánh giá độ phức tạp
- Số đỉnh $V = (M + 1) \times N$ $\le 2223 \times 10$ $= 22230$, số cạnh $E \approx 2 \times M \times N \le 44440$.
- Độ phức tạp thời gian của Dijkstra dùng hàng đợi ưu tiên là $O((V + E) \log V)$, chỉ khoảng vài trăm nghìn phép tính nên chạy rất nhanh (mình chạy thử test lớn nhất $M = 2222$, $N = 10$ thì C++ mất khoảng 0.02 giây, Python khoảng 0.1 giây).
- Độ phức tạp không gian: $O(V + E)$ cho danh sách kề và mảng
dist.
- Không biết học gì trước, học gì sau, học lan man nên kiến thức bị hổng.
- Bí bài không ai gỡ, ngồi cả buổi vẫn không ra rồi bỏ cuộc.
- Code chạy được nhưng không đủ điểm, không biết sai ở đâu, chậm ở đâu.
- Không có đề thi thử và người chấm, nên không biết mình đang ở mức nào.
- Dễ nản giữa chừng khi không có ai theo sát.
- Lộ trình 111 buổi video sắp sẵn từ dòng code đầu tiên đến đề HSG thật của các tỉnh.
- Thầy trực tiếp gỡ bí, chữa bài cho từng em qua nhóm Zalo riêng.
- Hệ thống chấm bài tự động 24/7 và thi thử HSG hằng tuần để biết chính xác mình đang ở đâu.
- Đóng học phí một lần, thầy kèm lâu dài: từ thi chuyên Tin đến HSG quốc gia và cả khi lên đại học.
Nhận xét
Đăng nhận xét