Bài 21: Đồ thị nhập môn trong C++ và Python: danh sách kề, BFS, DFS, thành phần liên thông và loang trên lưới
Lời mở đầu
Các bài từ đầu series tới giờ đều làm việc trên mảng và xâu: dữ liệu xếp thành một hàng. Nhưng rất nhiều bài toán có dữ liệu dạng mạng lưới: các thành phố nối với nhau bằng đường, các bạn học sinh quen nhau, các ô của mê cung kề nhau. Mô hình toán học cho chúng là đồ thị (graph): một tập đỉnh và các cạnh nối các cặp đỉnh.
Bài này là bài nhập môn đồ thị: em học cách lưu một đồ thị trong chương trình và hai cách duyệt nó: BFS (duyệt theo chiều rộng, lan dần như vết dầu loang) và DFS (duyệt theo chiều sâu, đi tới tận cùng rồi mới quay lui). Đặc biệt, BFS tìm được đường đi ngắn nhất trên lưới, dạng bài “mê cung” rất hay gặp.
dx, dy. Có khung chạy tay BFS, DFS, mô phỏng loang trong mê cung và 8 bài tập kèm gợi ý.Lộ trình bài viết, bấm vào từng phần để nhảy tới:
Phần 1Đồ thị là gì?
Một đồ thị gồm $n$ đỉnh (thường đánh số $1..n$) và $m$ cạnh, mỗi cạnh nối hai đỉnh. Đồ thị vô hướng khi cạnh đi được cả hai chiều (đường hai chiều, quan hệ bạn bè); có hướng khi cạnh chỉ đi một chiều (đường một chiều, quan hệ “theo dõi” trên mạng xã hội). Hai đỉnh có cạnh nối là kề nhau; số cạnh nối với một đỉnh là bậc của đỉnh đó. Một dãy đỉnh liên tiếp kề nhau là một đường đi. Các đỉnh đi tới được nhau tạo thành một thành phần liên thông.
Phần 2Lưu đồ thị: ma trận kề và danh sách kề
Đề bài thường cho đồ thị dưới dạng: dòng đầu $n$, $m$; $m$ dòng sau mỗi dòng hai số $u$, $v$ là một cạnh. Có hai cách lưu:
- Ma trận kề
a[u][v] = 1nếu có cạnh $u$ – $v$. Kiểm tra hai đỉnh có kề nhau trong $O(1)$, nhưng tốn $n^2$ ô nhớ: chỉ dùng được khi $n \le$ khoảng 5000. - Danh sách kề
vector<int> adj[N]:adj[u]chứa các đỉnh kề với $u$. Tốn $O(n + m)$ bộ nhớ, duyệt các đỉnh kề của $u$ nhanh. Đây là cách dùng mặc định khi thi.
Đọc đồ thị vô hướng vào danh sách kề
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
vector<int> adj[N]; // danh sách kề
int main(){
int n, m;
cin >> n >> m;
for(int i = 0; i < m; i++){
int u, v;
cin >> u >> v;
adj[u].push_back(v);
adj[v].push_back(u); // đồ thị vô hướng: thêm cả hai chiều
}
for(int u = 1; u <= n; u++){
cout << u << ":";
for(int v : adj[u]) cout << " " << v;
cout << "\n";
}
return 0;
}
n, m = map(int, input().split())
adj = [[] for _ in range(n + 1)]
for _ in range(m):
u, v = map(int, input().split())
adj[u].append(v)
adj[v].append(u)
for u in range(1, n + 1):
print(f"{u}:", *adj[u])
Phần 3BFS: duyệt theo chiều rộng và đường đi ngắn nhất
BFS (Breadth-First Search) bắt đầu từ đỉnh $s$, thăm tất cả các đỉnh cách $s$ 1 cạnh, rồi tới các đỉnh cách 2 cạnh, rồi 3 cạnh… giống như ném hòn đá xuống mặt hồ, sóng lan ra từng vòng. Để thăm “theo từng vòng” ta dùng hàng đợi (queue, bài 11): lấy đỉnh ở đầu hàng ra, đưa các đỉnh kề chưa thăm vào cuối hàng.
Hệ quả quan trọng: khi một đỉnh được thăm lần đầu, số cạnh đã đi chính là khoảng cách ngắn nhất từ $s$ tới nó (với đồ thị không có trọng số). Ta lưu d[v] = d[u] + 1 khi đưa $v$ vào hàng đợi từ $u$; d[v] = -1 nghĩa là chưa thăm. Mỗi đỉnh vào hàng một lần, mỗi cạnh được xét hai lần: $O(n + m)$.
Code C++ và Python
#include <bits/stdc++.h>
using namespace std;
vector<int> adj[100005];
int d[100005];
int main(){
int n, m, s;
cin >> n >> m >> s;
for(int i = 0; i < m; i++){
int u, v; cin >> u >> v;
adj[u].push_back(v); adj[v].push_back(u);
}
for(int i = 1; i <= n; i++) d[i] = -1;
queue<int> q;
d[s] = 0; q.push(s);
while(!q.empty()){
int u = q.front(); q.pop();
for(int v : adj[u]){
if(d[v] == -1){
d[v] = d[u] + 1;
q.push(v);
}
}
}
for(int i = 1; i <= n; i++) cout << d[i] << " ";
return 0;
}
from collections import deque
import sys
data = sys.stdin.read().split()
n, m, s = int(data[0]), int(data[1]), int(data[2])
adj = [[] for _ in range(n + 1)]
for i in range(m):
u, v = int(data[3 + 2 * i]), int(data[4 + 2 * i])
adj[u].append(v); adj[v].append(u)
d = [-1] * (n + 1)
d[s] = 0
q = deque([s])
while q:
u = q.popleft()
for v in adj[u]:
if d[v] == -1:
d[v] = d[u] + 1
q.append(v)
print(*d[1:])
Mô phỏng trực quan. Đọc code BFS thì chỉ thấy một hàng đợi và vòng while, khó hình dung vì sao nó cho ra đường đi ngắn nhất. Em bấm Tiếp để xem BFS loang ra từng lớp: đỉnh nào vào hàng đợi trước thì được xử lý trước, và nhãn d = … chính là khoảng cách tới đỉnh xuất phát:
Phần 4DFS: duyệt theo chiều sâu và thành phần liên thông
DFS (Depth-First Search) đi theo một hướng xa nhất có thể: từ đỉnh $u$ chọn một đỉnh kề chưa thăm, đi sang đó, rồi lại chọn tiếp… khi không còn đỉnh kề chưa thăm thì quay lui về đỉnh trước. Viết bằng đệ quy rất ngắn, đúng tinh thần quay lui ở bài 15.
DFS (hay BFS) từ một đỉnh thăm được đúng thành phần liên thông chứa đỉnh đó. Muốn đếm số thành phần: duyệt các đỉnh $1..n$, gặp đỉnh nào chưa thăm thì tăng bộ đếm và DFS từ nó để đánh dấu cả thành phần.
Code C++ và Python
#include <bits/stdc++.h>
using namespace std;
vector<int> adj[100005];
bool tham[100005];
void dfs(int u){
tham[u] = true;
for(int v : adj[u])
if(!tham[v]) dfs(v);
}
int main(){
int n, m;
cin >> n >> m;
for(int i = 0; i < m; i++){
int u, v; cin >> u >> v;
adj[u].push_back(v); adj[v].push_back(u);
}
int dem = 0;
for(int u = 1; u <= n; u++){
if(!tham[u]){
dem++;
dfs(u);
}
}
cout << dem;
return 0;
}
import sys
sys.setrecursionlimit(300000)
data = sys.stdin.read().split()
n, m = int(data[0]), int(data[1])
adj = [[] for _ in range(n + 1)]
for i in range(m):
u, v = int(data[2 + 2 * i]), int(data[3 + 2 * i])
adj[u].append(v); adj[v].append(u)
tham = [False] * (n + 1)
def dfs(u):
tham[u] = True
for v in adj[u]:
if not tham[v]:
dfs(v)
dem = 0
for u in range(1, n + 1):
if not tham[u]:
dem += 1
dfs(u)
print(dem)
Mô phỏng trực quan. DFS đệ quy “đi sâu rồi quay lui” nghe thì dễ, nhưng máy nhớ đường quay về bằng cách nào? Bằng ngăn xếp lời gọi hàm. Em hãy theo dõi ngăn xếp đó bên dưới đồ thị, đồng thời xem vòng lặp ngoài đếm từng thành phần liên thông:
Phần 5Loang trên lưới: mê cung và đếm vùng
Lưới ô vuông là một đồ thị “ngầm”: mỗi ô là một đỉnh, hai ô chung cạnh là kề nhau. Ta không cần tạo danh sách kề, chỉ cần mảng hướng:
int dx[4] = {-1, 1, 0, 0}; // lên, xuống, trái, phải
int dy[4] = {0, 0, -1, 1};
for(int k = 0; k < 4; k++){
int x = u.first + dx[k], y = u.second + dy[k];
if(x < 0 || x >= m || y < 0 || y >= n) continue; // ra ngoài lưới
if(g[x][y] == '#' || d[x][y] != -1) continue; // tường hoặc đã thăm
d[x][y] = d[u.first][u.second] + 1;
q.push({x, y});
}
BFS từ ô xuất phát cho khoảng cách ngắn nhất tới mọi ô; đi ngược theo các ô có khoảng cách giảm dần 1 sẽ ra đường đi. Muốn đếm số “vùng” (hòn đảo, căn phòng…) thì làm như đếm thành phần liên thông. Thêm 4 hướng chéo vào dx, dy thì thành lưới 8 hướng; đổi thành 8 bước nhảy của quân mã thì giải bài quân mã.
Đường đi ngắn nhất trong mê cung bằng BFS
#include <bits/stdc++.h>
using namespace std;
int m, n, d[1005][1005];
string g[1005];
int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
int main(){
cin >> m >> n;
int sx = 0, sy = 0, ex = 0, ey = 0;
for(int i = 0; i < m; i++){
cin >> g[i];
for(int j = 0; j < n; j++){
if(g[i][j] == 'S'){ sx = i; sy = j; }
if(g[i][j] == 'E'){ ex = i; ey = j; }
d[i][j] = -1;
}
}
queue<pair<int,int>> q;
d[sx][sy] = 0; q.push({sx, sy});
while(!q.empty()){
auto [x, y] = q.front(); q.pop();
for(int k = 0; k < 4; k++){
int u = x + dx[k], v = y + dy[k];
if(u < 0 || u >= m || v < 0 || v >= n) continue;
if(g[u][v] == '#' || d[u][v] != -1) continue;
d[u][v] = d[x][y] + 1;
q.push({u, v});
}
}
cout << d[ex][ey]; // -1 nếu không tới được
return 0;
}
from collections import deque
m, n = map(int, input().split())
g = [input().strip() for _ in range(m)]
d = [[-1] * n for _ in range(m)]
for i in range(m):
for j in range(n):
if g[i][j] == 'S': sx, sy = i, j
if g[i][j] == 'E': ex, ey = i, j
d[sx][sy] = 0
q = deque([(sx, sy)])
while q:
x, y = q.popleft()
for dx, dy in ((-1, 0), (1, 0), (0, -1), (0, 1)):
u, v = x + dx, y + dy
if 0 <= u < m and 0 <= v < n and g[u][v] != '#' and d[u][v] == -1:
d[u][v] = d[x][y] + 1
q.append((u, v))
print(d[ex][ey])
Xem “vết dầu loang” trong mê cung: bấm vào một ô để đặt hoặc xóa tường, rồi bấm Tự chạy. Màu càng đậm là càng xa ô xuất phát S; khi chạm tới E, đường đi ngắn nhất được tô sáng. Thử chuyển sang DFS để thấy DFS đi “lung tung” và không cho đường ngắn nhất.
- Covid (ICTU P58) (Trung bình)
- Lá Bài (LABAI) (Trung bình)
- The Time When the Network Becomes Idle (LeetCode 2039) (Trung bình)
- Nhà Từ Thiện Swyrin (Trung bình)
- Số Cách Đi Quân Mã (SOCACH) (Khá)
Bài tậpLuyện đồ thị
Cho đồ thị vô hướng $n$ đỉnh, $m$ cạnh ($n, m \le 10^5$). In bậc của các đỉnh $1..n$.
4 4
1 2
1 3
2 3
3 42 2 3 1💡 Gợi ý
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;
int main(){
int n, m;
cin >> n >> m;
vector<int> deg(n + 1, 0);
for(int i = 0; i < m; i++){ int u, v; cin >> u >> v; deg[u]++; deg[v]++; }
for(int u = 1; u <= n; u++) cout << deg[u] << (u < n ? " " : "\n");
return 0;
}
import sys
data = list(map(int, sys.stdin.read().split()))
n, m = data[0], data[1]
deg = [0] * (n + 1)
for i in range(m):
deg[data[2 + 2 * i]] += 1
deg[data[3 + 2 * i]] += 1
print(*deg[1:])
Cho đồ thị vô hướng $n$ đỉnh, $m$ cạnh và hai đỉnh $s$, $t$. In YES nếu có đường đi từ $s$ tới $t$, ngược lại NO.
5 3 1 4
1 2
2 3
4 5NO💡 Gợi ý
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;
int main(){
int n, m, s, t;
cin >> n >> m >> s >> t;
vector<vector<int>> adj(n + 1);
for(int i = 0; i < m; i++){ int u, v; cin >> u >> v; adj[u].push_back(v); adj[v].push_back(u); }
vector<bool> tham(n + 1, false);
queue<int> q; q.push(s); tham[s] = true;
while(!q.empty()){
int u = q.front(); q.pop();
for(int v : adj[u]) if(!tham[v]){ tham[v] = true; q.push(v); }
}
cout << (tham[t] ? "YES" : "NO");
return 0;
}
from collections import deque
import sys
data = list(map(int, sys.stdin.read().split()))
n, m, s, t = data[:4]
adj = [[] for _ in range(n + 1)]
for i in range(m):
u, v = data[4 + 2 * i], data[5 + 2 * i]
adj[u].append(v); adj[v].append(u)
tham = [False] * (n + 1)
tham[s] = True
q = deque([s])
while q:
u = q.popleft()
for v in adj[u]:
if not tham[v]:
tham[v] = True
q.append(v)
print("YES" if tham[t] else "NO")
Lớp có $n$ học sinh và $m$ cặp bạn thân ($n, m \le 10^5$). Bạn của bạn cũng là cùng nhóm. Lớp có bao nhiêu nhóm bạn? (Học sinh không có bạn nào tự tạo một nhóm.)
6 3
1 2
2 3
5 63💡 Gợi ý
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;
int main(){
int n, m;
cin >> n >> m;
vector<vector<int>> adj(n + 1);
for(int i = 0; i < m; i++){ int u, v; cin >> u >> v; adj[u].push_back(v); adj[v].push_back(u); }
vector<bool> tham(n + 1, false);
int dem = 0;
for(int s = 1; s <= n; s++){
if(tham[s]) continue;
dem++;
queue<int> q; q.push(s); tham[s] = true;
while(!q.empty()){
int u = q.front(); q.pop();
for(int v : adj[u]) if(!tham[v]){ tham[v] = true; q.push(v); }
}
}
cout << dem;
return 0;
}
from collections import deque
import sys
data = list(map(int, sys.stdin.read().split()))
n, m = data[0], data[1]
adj = [[] for _ in range(n + 1)]
for i in range(m):
u, v = data[2 + 2 * i], data[3 + 2 * i]
adj[u].append(v); adj[v].append(u)
tham = [False] * (n + 1)
dem = 0
for s in range(1, n + 1):
if tham[s]:
continue
dem += 1
tham[s] = True
q = deque([s])
while q:
u = q.popleft()
for v in adj[u]:
if not tham[v]:
tham[v] = True
q.append(v)
print(dem)
Đất nước có $n$ thành phố, $m$ con đường hai chiều ($n, m \le 10^5$). Thủ đô là thành phố 1. In số con đường ít nhất phải đi từ thủ đô tới từng thành phố $1..n$ ($-1$ nếu không tới được).
6 5
1 2
1 3
2 4
3 4
4 50 1 1 2 3 -1💡 Gợi ý
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;
int main(){
int n, m;
cin >> n >> m;
vector<vector<int>> adj(n + 1);
for(int i = 0; i < m; i++){ int u, v; cin >> u >> v; adj[u].push_back(v); adj[v].push_back(u); }
vector<int> d(n + 1, -1);
queue<int> q; q.push(1); d[1] = 0;
while(!q.empty()){
int u = q.front(); q.pop();
for(int v : adj[u]) if(d[v] == -1){ d[v] = d[u] + 1; q.push(v); }
}
for(int i = 1; i <= n; i++) cout << d[i] << (i < n ? " " : "\n");
return 0;
}
from collections import deque
import sys
data = list(map(int, sys.stdin.read().split()))
n, m = data[0], data[1]
adj = [[] for _ in range(n + 1)]
for i in range(m):
u, v = data[2 + 2 * i], data[3 + 2 * i]
adj[u].append(v); adj[v].append(u)
d = [-1] * (n + 1)
d[1] = 0
q = deque([1])
while q:
u = q.popleft()
for v in adj[u]:
if d[v] == -1:
d[v] = d[u] + 1
q.append(v)
print(*d[1:])
Bản đồ $m \times n$ ($m, n \le 1000$), # là đất, . là nước. Các ô đất chung cạnh thuộc cùng một hòn đảo. Đếm số hòn đảo.
4 5
##..#
#...#
..#..
.....3💡 Gợi ý
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;
int main(){
int m, n;
cin >> m >> n;
vector<string> g(m);
for(auto &s : g) cin >> s;
vector<vector<bool>> tham(m, vector<bool>(n, false));
int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1}, dem = 0;
for(int i = 0; i < m; i++)
for(int j = 0; j < n; j++){
if(g[i][j] != '#' || tham[i][j]) continue;
dem++;
queue<pair<int,int>> q; q.push({i, j}); tham[i][j] = true;
while(!q.empty()){
auto [x, y] = q.front(); q.pop();
for(int k = 0; k < 4; k++){
int u = x + dx[k], v = y + dy[k];
if(u >= 0 && u < m && v >= 0 && v < n && g[u][v] == '#' && !tham[u][v]){
tham[u][v] = true; q.push({u, v});
}
}
}
}
cout << dem;
return 0;
}
from collections import deque
m, n = map(int, input().split())
g = [input().strip() for _ in range(m)]
tham = [[False] * n for _ in range(m)]
dem = 0
for i in range(m):
for j in range(n):
if g[i][j] != '#' or tham[i][j]:
continue
dem += 1
tham[i][j] = True
q = deque([(i, j)])
while q:
x, y = q.popleft()
for u, v in ((x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)):
if 0 <= u < m and 0 <= v < n and g[u][v] == '#' and not tham[u][v]:
tham[u][v] = True
q.append((u, v))
print(dem)
Mê cung $m \times n$ ($m, n \le 1000$) gồm . (đường), # (tường), một ô S và một ô E. Mỗi bước đi sang ô chung cạnh. In số bước ít nhất từ S tới E, hoặc $-1$.
4 5
S.#..
.##.#
...#E
#....8💡 Gợi ý
E và S cũng đi được (chỉ chặn #).Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;
int main(){
int m, n;
cin >> m >> n;
vector<string> g(m);
for(auto &s : g) cin >> s;
vector<vector<int>> d(m, vector<int>(n, -1));
int sx = 0, sy = 0, ex = 0, ey = 0;
for(int i = 0; i < m; i++) for(int j = 0; j < n; j++){
if(g[i][j] == 'S'){ sx = i; sy = j; }
if(g[i][j] == 'E'){ ex = i; ey = j; }
}
int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
queue<pair<int,int>> q; q.push({sx, sy}); d[sx][sy] = 0;
while(!q.empty()){
auto [x, y] = q.front(); q.pop();
for(int k = 0; k < 4; k++){
int u = x + dx[k], v = y + dy[k];
if(u >= 0 && u < m && v >= 0 && v < n && g[u][v] != '#' && d[u][v] == -1){
d[u][v] = d[x][y] + 1; q.push({u, v});
}
}
}
cout << d[ex][ey];
return 0;
}
from collections import deque
m, n = map(int, input().split())
g = [input().strip() for _ in range(m)]
for i in range(m):
for j in range(n):
if g[i][j] == 'S': sx, sy = i, j
if g[i][j] == 'E': ex, ey = i, j
d = [[-1] * n for _ in range(m)]
d[sx][sy] = 0
q = deque([(sx, sy)])
while q:
x, y = q.popleft()
for u, v in ((x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)):
if 0 <= u < m and 0 <= v < n and g[u][v] != '#' and d[u][v] == -1:
d[u][v] = d[x][y] + 1
q.append((u, v))
print(d[ex][ey])
Bàn cờ $n \times n$ ($n \le 1000$). Quân mã ở ô $(x_1, y_1)$, mỗi bước đi theo hình chữ L (8 kiểu). Cần ít nhất bao nhiêu bước để tới ô $(x_2, y_2)$? (Tọa độ từ 1.)
8
1 1 8 86💡 Gợi ý
dx = {1, 2, 2, 1, -1, -2, -2, -1}, dy = {2, 1, -1, -2, -2, -1, 1, 2}.Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;
int main(){
int n, x1, y1, x2, y2;
cin >> n >> x1 >> y1 >> x2 >> y2;
vector<vector<int>> d(n + 1, vector<int>(n + 1, -1));
int dx[8] = {1, 2, 2, 1, -1, -2, -2, -1}, dy[8] = {2, 1, -1, -2, -2, -1, 1, 2};
queue<pair<int,int>> q; q.push({x1, y1}); d[x1][y1] = 0;
while(!q.empty()){
auto [x, y] = q.front(); q.pop();
for(int k = 0; k < 8; k++){
int u = x + dx[k], v = y + dy[k];
if(u >= 1 && u <= n && v >= 1 && v <= n && d[u][v] == -1){
d[u][v] = d[x][y] + 1; q.push({u, v});
}
}
}
cout << d[x2][y2];
return 0;
}
from collections import deque
n = int(input())
x1, y1, x2, y2 = map(int, input().split())
d = [[-1] * (n + 1) for _ in range(n + 1)]
d[x1][y1] = 0
q = deque([(x1, y1)])
moves = ((1, 2), (2, 1), (2, -1), (1, -2), (-1, -2), (-2, -1), (-2, 1), (-1, 2))
while q:
x, y = q.popleft()
for a, b in moves:
u, v = x + a, y + b
if 1 <= u <= n and 1 <= v <= n and d[u][v] == -1:
d[u][v] = d[x][y] + 1
q.append((u, v))
print(d[x2][y2])
Khu rừng $m \times n$ ($m, n \le 1000$): F là ô đang cháy, T là cây, . là đất trống. Mỗi phút lửa lan từ ô cháy sang các ô cây chung cạnh. Sau bao nhiêu phút mọi cây đều cháy? Nếu có cây không bao giờ cháy, in $-1$.
3 4
FTT.
.T.T
TTTF3💡 Gợi ý
F vào hàng đợi ngay từ đầu với khoảng cách 0, rồi BFS bình thường chỉ đi vào ô T. Đáp án là khoảng cách lớn nhất; nếu còn ô T chưa thăm thì in $-1$. Lửa từ nhiều nơi lan đồng thời, đúng như BFS lan theo từng vòng.Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;
int main(){
int m, n;
cin >> m >> n;
vector<string> g(m);
for(auto &s : g) cin >> s;
vector<vector<int>> d(m, vector<int>(n, -1));
queue<pair<int,int>> q;
for(int i = 0; i < m; i++) for(int j = 0; j < n; j++)
if(g[i][j] == 'F'){ d[i][j] = 0; q.push({i, j}); }
int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1}, kq = 0;
while(!q.empty()){
auto [x, y] = q.front(); q.pop();
kq = max(kq, d[x][y]);
for(int k = 0; k < 4; k++){
int u = x + dx[k], v = y + dy[k];
if(u >= 0 && u < m && v >= 0 && v < n && g[u][v] == 'T' && d[u][v] == -1){
d[u][v] = d[x][y] + 1; q.push({u, v});
}
}
}
for(int i = 0; i < m; i++) for(int j = 0; j < n; j++)
if(g[i][j] == 'T' && d[i][j] == -1){ cout << -1; return 0; }
cout << kq;
return 0;
}
from collections import deque
m, n = map(int, input().split())
g = [input().strip() for _ in range(m)]
d = [[-1] * n for _ in range(m)]
q = deque()
for i in range(m):
for j in range(n):
if g[i][j] == 'F':
d[i][j] = 0
q.append((i, j))
kq = 0
while q:
x, y = q.popleft()
kq = max(kq, d[x][y])
for u, v in ((x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)):
if 0 <= u < m and 0 <= v < n and g[u][v] == 'T' and d[u][v] == -1:
d[u][v] = d[x][y] + 1
q.append((u, v))
if any(g[i][j] == 'T' and d[i][j] == -1 for i in range(m) for j in range(n)):
print(-1)
else:
print(kq)
- 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