You are on page 1of 20

Bài toán kiểm tra tính liên thông

mạnh của đồ thị có hướng


A.Đặt bài toán
Đồ thị có hướng G=<V,E> liên thông mạnh nếu giữa hai đỉnh bất kỳ của nó đều
tồn tại đường đi. Cho trước đồ thị có hướng G = <V,E>. Nhiệm vụ của ta là kiểm tra
xem G có liên thông mạnh hay không?
B. Mô tả thuật toán
Đối với đồ thị vô hướng, nếu hai thủ tục DFS(u) = V hoặc BFS(u) = V thì ta kết
luận đồ thị vô hướng liên thông. Đối với đồ thị có hướng, nếu DFS(u)=V hoặc
BFS(u)=V thì ta mới chỉ có kết luận có đường đi từ u đến tất cả các đỉnh còn lại của
đồ thị. Nhiệm vụ của ta là phải kiểm tra DFS(u)=V hoặc BFS(u)=V với mọi uV.

def reset():
pass # Định nghĩa hàm reset tương ứng

def DFS(u):
pass # Định nghĩa hàm DFS tương ứng

def strong_connective(G):
reset()
for u in V:
if DFS(u) != V:
return False
reset()
return True

# Sử dụng hàm strong_connective với đồ thị G


is_strong_connective = strong_connective(G)
print(is_strong_connective)

C. Các ứng dụng của bài toán tính liên thông của đồ
thị có hướng
1. Kiểm tra tính liên thông của đồ thị có hướng bằng cách kiểm tra DFS(u)=V,
BFS(u)=V mọi uV
Ví dụ: Cho đồ thị có hướng G=(V,E) như sau:
Xét đỉnh 1: DFS(1):

STT Stack Các đỉnh được duyệt


1 1 1
2 1,6 1,6
3 1,6,10 1,6,10
4 1,6,10,2 1,6,10,2
5 1,6,10,2,3 1,6,10,2,3
6 1,6,10,2,3,9 1,6,10,2,3,9
7 1,6,10,2,3,9,5 1,6,10,2,3,9,5
8 1,6,10,2,3,9,5,7 1,6,10,2,3,9,5,7
9 1,6,10,2,3,9,5,7,11 1,6,10,2,3,9,5,7,11
10 1,6,10,2,3,9,5,7,11,8 1,6,10,2,3,9,5,7,11,8
11 1,6,10,2,3,9,5,7,11,8,4 1,6,10,2,3,9,5,7,11,8,4
12 1,6,10,2,3,9,5,7,11,8,12 1,6,10,2,3,9,5,7,11,8,12
13 1,6,10,2,3,9,5,7,13 1,6,10,2,3,9,5,7,11,8,12,13
Θ

Ta thấy từ đỉnh 1 có thể đi tới tất cả các đỉnh còn lại của đồ thị
 Đồ thị có tính liên thông
Xét đỉnh 2: DFS(2):

STT Stack Các đỉnh được duyệt


1 2 2
2 2,3 2,3
3 2,3,9 2,3,9
4 2,3,9,5 2,3,9,5
5 2,3,9,5,7 2,3,9,5,7
6 2,3,9,5,7,11 2,3,9,5,7,11
7 2,3,9,5,7,11,8 2,3,9,5,7,11,8
8 2,3,9,5,7,11,8,4 2,3,9,5,7,11,8,4
9 2,3,9,5,7,11,8,4,1 2,3,9,5,7,11,8,4,1
10 2,3,9,5,7,11,8,4,1,6 2,3,9,5,7,11,8,4,1,6
11 2,3,9,5,7,11,8,4,6,10 2,3,9,5,7,11,8,4,1,6,10
12 2,3,9,5,7,11,8,4,6,10,12 2,3,9,5,7,11,8,4,1,6,10,12
13 2,3,9,5,7,13 2,3,9,5,7,11,8,4,1,6,10,12,13
Θ

Ta thấy từ đỉnh 2 có thể đi tới tất cả các đỉnh còn lại của đồ thị
 Đồ thị có tính liên thông

Xét đỉnh 3: DFS(3):

STT Stack Các đỉnh được duyệt


1 3 3
2 3,9 3,9
3 3,9,5 3,9,5
4 3,9,5,7 3,9,5,7
5 3,9,5,7,11 3,9,5,7,11
6 3,9,5,7,11,2 3,9,5,7,11,2
7 3,9,5,7,11,2,8 3,9,5,7,11,2,8
8 3,9,5,7,11,2,8,4 3,9,5,7,11,2,8,4
9 3,9,5,7,11,2,8,4,1 3,9,5,7,11,2,8,4,1
10 3,9,5,7,11,2,8,4,1,6 3,9,5,7,11,2,8,4,1,6
11 3,9,5,7,11,2,8,4,1,6,10, 3,8,5,7,11,2,8,4,1,6,10
12 3,9,5,7,11,2,8,4,1,6,12 3,8,5,7,11,2,8,4,1,6,10,12
13 3,9,5,7,13 3,8,5,7,11,2,8,4,1,6,10,13
θ
Ta thấy từ đỉnh 3 có thể đi tới tất cả các đỉnh còn lại của đồ thị
 Đồ thị có tính liên thông

Tương tự với các đỉnh còn lại ta có bảng sau:

Đỉnh u DFS(u) DFS(u)=V?


1 1, 6, 10, 2, 3, 9, 5, 7, 11, 8, 4, 12, 13 YES
2 2, 3, 9, 5, 7, 11, 8, 4, 1, 6, 10, 12, 13 YES
3 3, 9, 5, 7, 11, 2, 8, 4, 1, 6, 10, 12, 13 YES
4 4, 1, 6, 10, 2, 3, 9, 5, 7, 11, 8, 12, 13 YES
5 5, 7, 11, 2, 3, 9, 13, 8, 4, 1, 6, 10, 12 YES
6 6, 10, 2, 3, 9, 5, 7, 11, 8, 4, 1, 12, 13 YES
7 7, 11, 2, 3, 9, 5, 13, 8, 4, 1, 6, 10, 12 YES
8 8, 4, 1, 6, 10, 2, 3, 9, 5, 7, 11, 13, 12 YES
9 9, 5, 7, 11, 2, 3, 13, 8, 4, 1, 6, 10, 12 YES
10 10, 2, 3, 9, 5, 7, 11, 8, 4, 1, 6, 12, 13 YES
11 11, 2, 3, 9, 5, 7, 13, 8, 4, 1, 6, 10, 12 YES
12 12, 4, 1, 6, 10, 2, 3, 9, 5, 7, 11, 8, 13 YES
13 13, 9, 5, 7, 11, 2, 3, 8, 4, 1, 6, 10, 12 YES

Cột ngoài cùng của Bảng có DFS(u) = V với mọi uV nên ta kết luận G liên
thông mạnh.

Nếu tại một hàng nào đó có DFS(u) V thì ta kết luận đồ thị không liên
thông mạnh và không cần phải kiểm tra tiếp các đỉnh còn lại.

Dưới đây là phần code để giải quyết bài toán trên bằng ngôn ngữ python:
Bước 1: Khởi tạo mảng:
ok = [False] * MAX_VERTICES: Khởi tạo một mảng ok chứa MAX_VERTICES
phần tử, mỗi phần tử được gán giá trị False để đánh dấu việc duyệt đỉnh.
ke = [[] for _ in range(MAX_VERTICES)]: Khởi tạo một mảng ke để lưu danh sách
kề của các đỉnh. Ban đầu, danh sách kề của mỗi đỉnh là một danh sách rỗng

Bước 2: Gọi hàm DFS sử dụng đệ quy

ok[u] = True: Đánh dấu đỉnh u đã được duyệt.


for v in ke[u]:: Duyệt qua các đỉnh kề của đỉnh u.
if not ok[v]:: Kiểm tra xem đỉnh kề v đã được duyệt chưa.
dfs(v): Gọi đệ quy hàm dfs với đỉnh v.
dem += 1: Tăng biến dem lên 1 sau khi duyệt một đỉnh.
Bước 3 : Xử lí hàm main

v, e = map(int, input().split()): Nhập số đỉnh v và số cạnh e từ người dùng.


dem = 1: Gán giá trị ban đầu cho biến dem là 1.
dfs(1): Gọi hàm dfs với đỉnh xuất phát là 1.
if dem == v:: Kiểm tra nếu số đỉnh đã duyệt bằng v.
print("YES"): In "YES" nếu số đỉnh đã duyệt bằng v.
else:: Ngược lại, nếu số đỉnh đã duyệt không bằng v.
print("NO"): In "NO".
Ngoài ra có thể sử dụng BFS để giải quyết bài toán :
Bước 1: Khởi tạo mảng:
MAX_VERTICES = 1005: Định nghĩa một hằng số MAX_VERTICES với giá trị
1005, tương đương với số lượng đỉnh tối đa.
ok = [False] * MAX_VERTICES: Khởi tạo một mảng ok gồm MAX_VERTICES
phần tử, mỗi phần tử được gán giá trị False.
ke = [[] for _ in range(MAX_VERTICES)]: Khởi tạo một mảng ke chứa danh sách kề
của các đỉnh, với MAX_VERTICES danh sách ban đầu trống.
Bước 2: Gọi Hàm BFS
q = deque(): Khởi tạo một hàng đợi q bằng deque.
ok[u] = True: Đánh dấu đỉnh xuất phát u đã được duyệt.
q.append(u): Thêm đỉnh xuất phát u vào hàng đợi.
while q:: Bắt đầu vòng lặp while với điều kiện là hàng đợi không rỗng.
t = q.popleft(): Lấy và loại bỏ đỉnh đầu tiên khỏi hàng đợi.
for i in range(len(ke[t])):: Duyệt qua các đỉnh kề của đỉnh t.
if not ok[ke[t][i]]:: Kiểm tra xem đỉnh kề đã được duyệt chưa.
q.append(ke[t][i]): Thêm đỉnh kề vào hàng đợi.
ok[ke[t][i]] = True: Đánh dấu đỉnh kề đã được duyệt.
dem += 1: Tăng biến dem lên 1.
Bước 3: Hàm main giải quyết bài toán:
v, e = map(int, input().split()): Nhập số đỉnh v và số cạnh e từ người dùng.
dem = 1: Gán giá trị ban đầu cho biến dem là 1.
ok = [False] * MAX_VERTICES: Khởi tạo lại mảng ok với giá trị False.
bfs(1): Gọi hàm bfs với đỉnh xuất phát là 1.
if dem == v:: Kiểm tra nếu số đỉnh đã duyệt bằng v.
print("YES"): In "YES" nếu số đỉnh đã duyệt bằng v.
else:: Ngược lại, nếu số đỉnh đã duyệt không bằng v.
print("NO"): In "NO".
2. Thuật toán Kosaraju

Theo định nghĩa chúng ta sẽ cần phải duyệt DFS hay BFS của tất cả các đỉnh thuộc
đồ thị G=(V,E) để kiểm tra tính liên thông mạnh của đồ thị nhưng cách này sẽ cần phải
duyệt nhiều DFS nên chúng ta có thuật toán Kosaraju để giải quyết bài toán ngắn gọn
hơn.

-Các bước thực hiện thuật toán Kosaraju:


B1: Lấy từ 1 đỉnh bất kì trên đồ thị và thực hiện DFS để duyệt tất cả các đỉnh của đồ
thị. Trong quá trình duyệt, lưu các đỉnh đã được thăm theo thứ tự ngược lại (từ cuối
cùng của DFS).
B2: Lấy kết quả từ bước 1, khởi tạo một đồ thị mới bằng cách đảo chiều các cạnh của
đồ thị ban đầu.
B3: Thực hiện DFS trên đồ thị mới với thứ tự các đỉnh được xác định từ bước 1. Khi
duyệt một thành phần liên thông trong DFS này, chúng ta đã tìm được một thành phần
liên thông mạnh của đồ thị gốc.
Giải quyết bài toán bằng ngôn ngữ python như sau:
Ví dụ: Cho đồ thị có hướng G=<V, E> được biểu diễn dưới dạng danh sách cạnh. Hãy
kiểm tra xem đồ thị có liên thông mạnh hay không?
Đầu vào: • Dòng đầu tiên đưa vào T là số lượng bộ test. Những dòng tiếp theo đưa vào
các bộ test. Mỗi bộ test gồm 2 dòng: dòng đầu tiên đưa vào hai số |VI, |E| tương ứng
với số đỉnh và số cạnh; Dòng tiếp theo đưa vào các bộ đôi u, v tương ứng với một
cạnh của đồ thị.
T, |V), |E| thỏa mãn ràng buộc: 1<1<=100; 1=|V|=10* 1=|E|<|V|(IVI-1)/2;
Đầu ra: • Đưa ra “YES” hoặc "NO" theo từng dòng tương ứng với test là liên thông
mạnh hoặc không liên thông mạnh.
Giải quyết bài toán bằng ngôn ngữ python như sau:
3. Thuật toán Tarjan
Đề bài
Cho đồ thị G(V,E) có hướng N (1≤N≤104) đỉnh, M (1≤M≤105) cung. Hãy đếm số
thành phần liên thông mạnh của G.
Input
 Dòng đầu tiên là N,M.
 M dòng tiếp theo mô tả một cung của G.
Output
 Gồm một dòng duy nhất là số TPLT mạnh.
Examples
Input
3 2
1 2
2 3
Output
3
Input
3 3
1 2
2 3
3 1
Output
1
Thuật toán Tarjan được xây dựng dựa trên các dữ kiện sau:
 Tìm kiếm DFS tạo ra cây/ rừng DFS
 Các thành phần liên thông mạnh tạo thành các cây con của cây DFS.
 Nếu ta có thể tìm được đỉnh gốc của các cây con như vậy, ta có thể in/ lưu trữ
tất cả các nút trong cây con đó (bao gồm cả đỉnh gốc) và đó sẽ là một thành
phần liên thông mạnh (Strongly Connected Components - SCC).
 Không có cung ngược từ SCC này sang SCC khác (Có thể có các cung chéo,
nhưng các cung chéo sẽ không được sử dụng trong khi xử lý đồ thị).
Mô tả thuật toán

Ý tưởng
 Đầu tiên ta thực hiện DFS kết hợp tính mảng low[],num[] như đã trình bày ở
trên. Song song với việc này, khi duyệt tới đỉnh u ta sẽ thực hiện
đẩy u vào stack.
 Khi đã duyệt xong đỉnh u (sau khi duyệt hết toàn bộ các đỉnh nằm trong cây
con DFS gốc u), nếu num[u]=low[u] thì đây chính là đỉnh có thứ tự thăm sớm
nhất của một thành phần liên thông mạnh.
 Khi đó ta sẽ loại bỏ tất cả các đỉnh trong thành phần liên thông mạnh này ra
khỏi đồ thị và các đỉnh này là các đỉnh đang nằm trên u trong stack hiện tại vì
các đỉnh này chính là các đỉnh nằm trên cây con gốc u trong cây DFS do các
nút được đẩy vào stack theo thứ tự thăm.
 Mặt khác, giả sử ta có đỉnh x thuộc cây con gốc u và x thuộc một thành phần
liên thông mạnh không chứa u có đỉnh có thứ tự thăm sớm nhất là y, dễ
thấy y phải là con của u nên thời điểm duyệt xong của y sớm hơn u chứng
tỏ y và thành phần liên thông mạnh chứa nó sẽ bị loại bỏ trước đó không còn
trong stack nữa.
 Ta sẽ đánh dấu tất cả các đỉnh thuộc thành phần liên thông mạnh này
bằng 11 mảng để sau này không xét lại đỉnh đấy nữa. Đồng thời, ta loại bỏ cách
đỉnh này ra khỏi stack để không làm ảnh hưởng tới các đỉnh khác vẫn còn nằm
trong đồ thị.

Cài đặt
Cấu trúc dữ liệu:
 Hằng số maxN = 100010
 Biến timeDfs - Thứ tự DFS
 Biến scc - Số lượng thành phần liên thông mạnh
 Mảng low[], num[]
 Mảng deleted[] - Đánh dấu các đỉnh đã bị xóa
 Vector g[] - Danh sách cạnh kề của mỗi đỉnh
 Ngăn xếp st - Lưu lại các đỉnh trong thành phần liên thông mạnh

#include <bits/stdc++.h>

using namespace std;

const int maxN = 100010;

int n, m;
int timeDfs = 0, scc = 0;
int low[maxN], num[maxN];
bool deleted[maxN];
vector <int> g[maxN];
stack <int> st;

void dfs(int u) {
num[u] = low[u] = ++timeDfs;
st.push(u);
for (int v : g[u]) {
if (deleted[v]) continue;
if (!num[v]){
dfs(v);
low[u] = min(low[u], low[v]);
}
else low[u] = min(low[u], num[v]);
}
if (low[u] == num[u]) {
scc++;
int v;
do {
v = st.top();
st.pop();
deleted[v] = true;
}
while (v != u);
}
}

int main() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
}
for (int i = 1; i <= n; i++)
if (!num[i]) dfs(i);

cout << scc;


}
Đánh giá
 Độ phức tạp của thuật toán Tarjan là O(N+M)

Ví dụ
Đề bài
Khu vườn của Đạt có hình chữ nhật, và được chia thành M⋅N ô vuông bằng nhau.
Trong mỗi ô vuông có một cây thuộc một loại quả khác nhau, đánh số từ 00 đến 99.
Những con số này thể hiện giá trị kinh tế của các loại cây. Tuy nhiên, nhìn mặt con
Robot trái cây này có vẻ ngu ngu nên trong lần đầu tiên thử việc, Pirate muốn test AI
của nó. Cụ thể là Robot phải tuân theo các quy định sau:
1. Tại mỗi ô, Robot chỉ có thể đi sang hướng đông hoặc hướng nam sang ô kề
cạnh.
2. Có một số ô đặc biệt mà tại đó Robot có thể đi được thêm hướng tây hoặc
hướng bắc sang ô kề cạnh (chỉ một trong hai).
3. Robot không được đi vào những ô có cây dừa.
4. Robot được đi qua một ô nhiều lần. Khi đi qua một ô, Robot phải hái hết quả ở
cây trong ô đó. Lợi nhuận thu được sẽ bằng chỉ số của loại cây vừa được thu
hái. Và sau này, không thể đạt thêm lợi nhuận gì từ ô đó nữa.

Xuất phát từ ô ở góc tây bắc của khu vườn, hãy giúp Robot trái cây xác định hành
trình để đạt được lợi nhuận tối đa.
Input
Dòng thứ nhất: ghi hai số nguyên M và N - kích thước của khu vườn.

M dòng tiếp theo: mỗi dòng ghi N kí tự liên tiếp nhau mô tả khu vườn: + '0' -

'9': các loại trái cây; + '#': cây dừa; + 'W': được quyền đi theo hướng tây; + 'N':
được quyền đi theo hướng bắc.
Output
 Ghi một số nguyên duy nhất là lợi nhuận tối đa đạt được.
Example
Input
2 3
264
3WW
Output
15
Note
Robot sẽ đi theo hành trình như sau (1,1)→(1,2)→(1,3)→(2,3)→(2,2)→(2,1)
(ô (i,j) là ô ở dòng i và cột j). Tổng lợi nhuận sẽ là 2+6+4+3=152+6+4+3=15.
Phân tích
 Mấu chốt của bài toán này là: Tìm ra được các thành phần liên thông mạnh, co
từng thành phần liên thông mạnh thành 11 đỉnh. Lúc này đồ thị mới sẽ là đồ thị
DAG (Directed Acyclic Graph). Đây là đồ thị "một đi không trở lại", vậy nên
ta dễ dàng QHĐ trên đồ thị DAG.
 Công thức QHĐ trên đồ thị DAG: f[u]=max(f[v])+C[u] với mọi u có cung trực
tiếp đi tới v; trong đó C[u] là tổng giá trị kinh tế của đỉnh u , f[u] là tổng giá trị
kinh tế lớn nhất khi ta xuất phát tại u và kết thúc tại 11 đỉnh bất kì vì ta có thể
đi từ u sang v rồi đi theo đường đi tối ưu xuất phát tại v (u,v ở đây là các đỉnh
trên đồ thị DAG được tạo ra).
Cài đặt
Cấu trúc dữ liệu:
 Để có thể dễ dàng cài đặt thì ta sẽ sử dụng kĩ thuật "Biến mảng 2 chiều thành
mảng 1 chiều" nhằm mục đích lưu giá trị ô (i,j) vào ô (i−1)⋅N+j
 Hằng số maxN = 100010
 Hằng số INF = 1000000007
 Biến timeDfs - Thứ tự DFS
 Biến scc - Số lượng thành phần liên thông mạnh
 Mảng a[] - Lưu các dữ liệu vào.
 Mảng val[] - Lưu giá trị kinh tế của loại cây.
 Mảng totalScc[] - Lưu tổng giá trị kinh tế của từng thành phần liên thông
mạnh.
 Mảng root[] - Lưu ô (i,j) thuộc thành phần liên thông nào? Ta sẽ lấy thứ tự của
thành phần liên thông làm đỉnh ảo trong đồ thị DAG.
 Mảng low[], num[]
 Mảng deleted[] - Đánh dấu các đỉnh đã bị xóa
 Mảng f[] - Mảng quy hoạch động
 Vector g[] - Lưu đồ thị ban đầu.
 Vector h[] - Lưu đồ thị mới (đồ thị DAG).
#include <bits/stdc++.h>

using namespace std;

const int maxN = 100010;


const int INF = 1e9 + 7;

int dx[] = {0, -1, 0, 1, 0};


int dy[] = {0, 0, 1, 0,-1};

int m, n;
char a[maxN];
int val[maxN], totalScc[maxN];

/* Lưu đồ thị ban đầu*/


vector <int> g[maxN];

/* Lưu đồ thị mới*/


vector <int> h[maxN];

/* Kỹ thuật "Biến mảng 2 chiều thành mảng 1 chiều" */


int getId(int i, int j){
return (i - 1) * n + j;
}

/* Kiểm tra ô (i, j) có được đi vào không? */


bool check(int i, int j) {
if (a[getId(i, j)] == '#') return false;
return (i >= 1 && j >= 1 && i <= m && j <= n);
}

/* Tìm thành phần liên thông mạnh*/


int root[maxN];
int low[maxN], num[maxN];
bool deleted[maxN];
int timeDfs = 0, scc = 0;
stack <int> st;

void dfs(int u) {
low[u] = num[u] = ++timeDfs;
st.push(u);
for (int v : g[u]) {
if (deleted[v]) continue;
if (!num[v]) {
dfs(v);
low[u] = min(low[u], low[v]);
}
else low[u] = min(low[u], num[v]);
}

if (num[u] == low[u]) {
scc++;
int v;
do {
v = st.top();
st.pop();
deleted[v] = true;

/* Tính tổng giá trị kinh tế của thành phần liên thông */
totalScc[scc] += val[v];

/*Đỉnh scc sẽ là đỉnh ảo đại diện cho v trong đồ thị DAG*/


root[v] = scc;
} while (v != u);
}
}

/* Quy hoạch động trên đồ thị DAG */


int f[maxN];

int solve(int u) {
if (h[u].empty()) return totalScc[u];
if (f[u] != -1) return f[u];
int cur = -INF;
for (int v : h[u]) cur = max(cur, solve(v) + totalScc[u]);
return f[u] = cur;
}

int main() {
/* Xử lý dữ liệu đầu vào */
cin >> m >> n;
for (int i = 1; i <= m; ++i)
for (int j = 1; j <= n; ++j) {
int u = getId(i, j);
cin >> a[u];
val[u] = (a[u] >= '0' && a[u] <= '9') ? a[u] - '0' : 0;
}

/* Xây dựng đồ thị ban đầu */


for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
int u = getId(i, j);
if (a[u] == '#') continue;
if (check(i, j + 1)) g[u].push_back(getId(i, j + 1));
if (check(i + 1, j)) g[u].push_back(getId(i + 1, j));

if (a[u] == 'W' && check(i, j - 1))


g[u].push_back(getId(i, j - 1));

if (a[u] == 'N' && check(i - 1, j))


g[u].push_back(getId(i - 1, j));
}
}

/* Tìm thành phần liên thông mạnh*/


for (int i = 1; i <= m; ++i)
for (int j = 1; j <= n; ++j){
int u = getId(i, j);
if (!num[u] && check(i, j)) dfs(u);
}

/* Xây dựng đồ thị mới */


for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
if (!check(i, j)) continue;
int u = getId(i, j);
int ru = root[u];
for (int v : g[u]) {
int rv = root[v];
if (ru != rv) {
/* Có cung đi từ ru đến rv trên đồ thị mới do đỉnh
u trong TPLTM ru đi được tới đỉnh v trong TPLTM rv*/
h[ru].push_back(rv);
}
}
}
}
fill(f, f + m * n + 1, -1);
cout << solve(root[getId(1, 1)]);
}
Đánh giá
 Độ phức tạp của bài toán là O(N⋅M)

You might also like