Tải bản đầy đủ

Chuyên đề DFS TK Chiều Sâu

ỨNG DỤNG THUẬT TOÁN TÌM KIẾM CHIỀU SÂU DFS GIẢI
MỘT SỐ BÀI TẬP TIN HỌC
I. Ý tưởng và cài đặt thuật toán DFS
Tư tưởng thuật toán có thể trình bày như sau: Từ một đỉnh S ban đầu ta sẽ có
các đỉnh kề là A, từ đỉnh A ta sẽ có các đỉnh kề là D, và nó cũng thuộc nhánh S-A-D…
Chúng ta thăm các nhánh đó theo chiều sâu (thăm đến khi không còn đỉnh kề chưa
duyệt). Điều đó gợi cho chúng ta viết một thủ tục đệ quy DFS(u) để mô tả việc duyệt từ
đỉnh u sang đỉnh kề v chưa được thăm.

1. Mô hình giải thuật DFS
Giải thuật DFS có thể viết theo mô hình dưới đây:
void dfs(int u)
{
free[u]=false; // đánh dấu đỉnh u đã được thăm
for (int v=1; v<=n; v++)
if ((tồn tại cạnh u, v) và (free[u][v]==true)) // tồn tại đỉnh kề với u, chưa được
1


thăm
dfs(v); //duyệt đỉnh v

}
2. Ví dụ minh họa
Cho đồ thị vô hướng gồm n đỉnh, m cạnh, các thành phần trên đồ thị liên thông với
nhau. Viết chương trình ghi ra thứ tự duyệt DFS xuất phát từ đỉnh s.
Dữ liệu vào:
 Dòng đầu: gồm 3 số nguyên n, m, s, t (1<=n, m<=100, 1<=s<=n)
 M dòng tiếp theo: mỗi dòng gồm 2 số u, v, mô tả 1 cạnh trong đồ thị
Dữ liệu ra:
 Gồm nhiều dòng là thứ tự đường đi từ s.
Ví dụ
Input
771

Output
1

12

2

13

4

15

6

24

5

26

3

37

7


56
Hướng dẫn:
-

Sử dụng thuật toán tìm kiếm chiều sâu (DFS).
#include
#include
using namespace std;
int a[101][101];
2


int n, m, Free[101], u, v, s;
void DFS(int u)
{
cout <Free[u] = false;
for (int v = 1; v<=n; v++)
if (a[u][v] ==1 && Free[v])
DFS(v);
}
int main()
{
freopen ("DFS.inp", "r", stdin);
freopen ("DFS.out", "w", stdout);
cin >> n >> m >> s;
for (int i=1; i<=n; i++)
for (int j=1; j<=n; j++)
a[i][j] = 0;
for (int i=1; i<=m; i++)
{
cin >> u >> v;
a[u][v] = 1;
a[v][u] = 1;
}
for (int i =1; i<=n; i++)
Free[i] = 1;
DFS(s);
//cout <return 0;
}
3. Độ phức tạp DFS
Độ phức tạp thời gian: O(|V|+|E|) với |V| là số đỉnh của đồ thị, |E| là số cạnh

3


II. Bài tập ứng dụng thuật toán DFS
Bài 1: Bãi cỏ ngon nhất - VBGRASS
Bessie dự định cả ngày sẽ nhai cỏ xuân và ngắm nhìn cảnh xuân trên cánh đồng
của nông dân John, cánh đồng này được chia thành các ô vuông nhỏ với R (1 <= R <=
100) hàng và C (1 <= C <= 100) cột. Bessie ước gì có thể đếm được số khóm cỏ trên
cánh đồng.
Mỗi khóm cỏ trên bản đồ được đánh dấu bằng một ký tự ‘#‘ hoặc là 2 ký tự ‘#’
nằm kề nhau (trên đường chéo thì không phải). Cho bản đồ của cánh đồng, hãy nói cho
Bessie biết có bao nhiêu khóm cỏ trên cánh đồng.
Ví dụ như cánh đồng dưới dây với R=5 và C=6:
.#....
..#...
..#..#
...##.
.#....
Cánh đồng này có 5 khóm cỏ: một khóm ở hàng đầu tiên, một khóm tạo bởi hàng
thứ 2 và thứ 3 ở cột thứ 2, một khóm là 1 ký tự nằm riêng rẽ ở hàng 3, một khóm tạo bởi
cột thứ 4 và thứ 5 ở hàng 4, và một khóm cuối cùng ở hàng 5.
Dữ liệu
 Dòng 1: 2 số nguyên cách nhau bởi dấu cách: R và C
 Dòng 2..R+1: Dòng i+1 mô tả hàng i của cánh đồng với C ký tự, các ký tự là ‘#’
hoặc ‘.’ .
Kết quả
 Dòng 1: Một số nguyên cho biết số lượng khóm cỏ trên cánh đồng.

4


Ví dụ
Input
.#....

Output
5

..#...
..#..#
...##.
.#....
Hướng dẫn
#include
#include
using namespace std;
int n, m;
int s=0;
char a[101][101];
bool Free[101][101];
int td[4] = {0, 0, -1, 1};
int tc[4] = {-1, 1, 0, 0};
void DFS (int u, int v)
{
int u1, v1;
Free[u][v] = true;
for (int i=1; i<=4; i++)
{
u1 = u + td[i];
v1 = v + tc[i];
if ((a[u1][v1] == '#') && (!Free[u1][v1] ))
DFS(u1,v1);
5


}
}
int main()
{
freopen("vbgrass.inp", "r", stdin);
freopen("vbgrass.out", "w", stdout);
cin >> m >> n;
for (int i=1; i<=m; i++)
for (int j=1; j<=n; j++)
cin >> a[i][j];
for (int i=1; i<=m; i++)
for (int j=1; j<=n; j++)
if ((a[i][j] == '#')&& (!Free[i][j]))
{
DFS(i,j);
s++;
}
cout << s;
}
Bài 2: ZEROPATH - Phân tích số
Mỗi một số nguyên dương đều có thể biểu diễn dưới dạng tích của 2 số nguyên
dương X,Y sao cho X<=Y . Nếu như trong phân tích này ta thay X bởi X-1 còn Y bởi
Y+1 thì sau khi tính tích của chúng ta thu được hoặc là một số nguyên dương mới hoặc là
số 0 .
Ví Dụ : Số 12 có 3 cách phân tích 1*12 ,3*4 , 2*6 . Cách phân tích thứ nhất cho ta
tích mới là 0 : (1-1)*(12+1) = 0 , cách phân tích thứ hai cho ta tích mới 10 : (3-1)*(4+1)
6


= 10 , còn cách phân tích thứ ba cho ta 7 : (2-1)*(6+1)=7 . Nếu kết quả là khác 0 ta lại
lặp lại thủ tục này đối với số thu được . Rõ ràng áp dụng liên tiếp thủ tục trên , cuối cùng
ta sẽ đến được số 0 , không phụ thuộc vào việc ta chọn cách phân tích nào để tiếp tục
Yêu cầu :

Cho trước số nguyên dương N ( 1<=N<=100000) , hãy đưa ra tất cả các số

nguyên dương khác nhau có thể gặp trong việc áp dụng thủ tục đã mô tả đối với N .
Dữ liệu :

1 dòng chứa số nguyên dương N .

Kết quả :

Gồm 2 dòng

Dòng đầu tiên ghi K là số lượng số tìm được
Dòng tiếp theo chứa K số tìm được theo thứ tự tăng dần bắt đầu từ số 0 .
Lưu ý :

Có thể có số xuất hiện trên nhiều đường biến đổi khác nhau , nhưng nó chỉ

được tính một lần trong kết quả .
Input
12

Output
6
0 3 4 6 7 10

Hướng dẫn:
-

Dùng thuật toán tìm kiếm theo chiều sâu
#include
#include
using namespace std;
int n, d[100000]={0};
void DFS(int n)
{
for (int v=1; v <= sqrt(n); v++)
{
if (n%v == 0)
{
int x = v-1;
int y = n/v + 1;
7


if (d[x*y] == 0)
{
d[x*y]=1;
DFS(x*y);
}
}
}
}
int main()
{
freopen("zeropath.inp", "r", stdin);
freopen("zeropath.out", "w", stdout);
int dem=0;
cin >> n;
DFS(n);
for (int i=0; i <=n; i++)
if (d[i] ==1) dem++;
cout <for (int i=0; i<=n; i++)
if (d[i] ==1) cout <}
Bài 3: ADS spoj – Quảng cáo
Nhân dịp Tết sắp đến công ty Jelly-for-Kids quyết định tăng cường việc quảng bá
sản phẩm đến người tiêu dùng. Vì thế giám đốc marketing, ông Fruit-Jelly muốn gửi đi số
lượng nhân viên tối đa có thể, làm nhiệm vụ tiếp thị tại đại lý trong thành phố
Trong thành phố có m con đường, n đại lý bán kẹo (đánh số từ 1 đến n). Mỗi con đường
chỉ nối trực tiếp giữa 2 đại lý, và được ký hiệu bằng chỉ số của 2 đại lý mà nó nối. Đồng
thời, giữa 2 đại lý bất kỳ có không quá 1 con đường nối chúng
8


Ông Fruit-Jelly nghĩ rằng, ông ta sẽ quản lý nhân viên dễ hơn nếu xếp mỗi người
tiếp thị trên những hành trình có tính chất thứ tự. Tức là những đại lý bán kẹo trên hành
trình đó thỏa các điều kiện sau
Có đường nối trực tiếp giữa 2 đại lý liên tiếp nhau trên hành trình
Từ một đại lý bất kỳ trong hành trình có thể đi qua tất cả các đoạn đường trong
hành trình đó rồi trở về nơi xuất phát mà không đi qua đoạn đường nào quá một lần
Hành trình phân công cho mỗi nhân viên phải có ít nhất một đoạn đường chưa có
nhân viên nào khác đi tiếp thị.
Mỗi nhân viên chỉ di chuyển trên hành trình mà anh ta được phân công. Hãy tính
số lượng nhân viên tối đa mà ông Fruit-Jelly có thể xếp việc, và hành trình cụ thể mà mỗi
người được xếp.
Input
Dòng đầu là 2 số tự nhiên N và M (N<=2000) (M<=5000)
Trong M dòng tiếp theo, mỗi dòng ghi 2 số nguyên mô tả một đoạn đường, mỗi đoạn
đường được mô tả bởi chỉ số của 2 đại lý mà nó nối.
Output
Dòng đầu tiên ghi Q là số lượng nhân viên tối đa tìm được
Input
56

Output
2

12
24
45
35
13
23
Hướng dẫn:
-

Dùng thuật toán tìm kiếm theo chiều sâu

9


#include
#include
using namespace std;
int n,m, res, dem, x, y;
int a[2000][2000];
bool Free[2000];
void DFS(int i)
{
Free[i]=true;
for (int j=1; j<=n; j++)
{
if ((a[i][j]==1) && (!Free[j]))
DFS(j);
}
}
int main()
{
freopen("ADS.inp" , "r", stdin);
freopen("ADS.out", "w", stdout);
cin >> n >> m;
for (int i=1; i<=m; i++)
{
cin >> x >> y;
a[x][y] =1;
a[y][x] =1;
}
for (int i=1; i<=n; i++)
if (!Free[i])
10


{
dem++;
DFS(i);
}
res = m - n + dem;
cout << res;
}
III. Bài tập tự làm
Bài 1: DS – ebola
Một cơ quan có n nhân viên được đánh số thứ tự từ 1 đến n. Mỗi người có một
phòng làm việc riêng của mình. Do nhu cầu công việc, hàng ngày mỗi nhân viên có thể
phải tiếp xúc với một số nhân viên khác. Vào một ngày làm việc bình thường, có một
nhân viên bị nhiễm bệnh Ebola, nhưng do không biết nên người này vẫn đi làm. Đến cuối
ngày làm việc người ta mới phát hiện ra người nhiễm bệnh Ebola đầu tiên, Khả năng lây
lan của Ebola rất nhanh chóng: một người nhiễm bệnh nếu tiếp xúc với một người khác
có thể sẽ truyền bệnh cho người này.
Yêu cầu: Hãy giúp các bác sĩ kiểm tra xem cuối ngày hôm đó, có tối đa bao nhiêu người
có thể sẽ nhiễm bệnh và đó là những người nào để còn cách ly. Người có tiếp xúc với
người nhiễm bệnh được coi là người nhiễm bệnh.


Dòng đầu tiên ghi 2 số tự nhiên n,k (1<=n<=10^5, 1<=k<=n) tương ứng là số
lượng người làm việc trong toà nhà và số hiệu của nhân viên đã nhiễm Ebola đầu tiên.



Dòng thứ i trong n dòng tiếp theo ghi danh sách những người có tiếp xúc với
người thứ i theo cách sau: số đầu tiên m của dòng là tổng số nhân viên đã gặp người thứ i,
tiếp theo là m số tự nhiên lần lượt là số hiệu của các nhân viên đó. Nếu m=0 có nghĩa
rằng không ai đã tiếp xúc với người thứ i.



Dữ liệu được cho đảm bảo tổng số lần tiếp xúc của tất cả nhân viên trong cơ quan
không vượt quá 10^6



Dòng đầu tiên ghi số s là tổng số người có thể bị lây nhiễm Ebola
11


Dòng thứ 2 liệt kê tất cả nhân viên có thể bị lây nhiễm Ebola cần cách ly, danh



sách cần được sắp theo thứ tự tăng dần của số hiệu nhân viên.
Input
51

Output
3

223

123

213
212
15
14
Bài 2: Đếm số ao - BCLKCOUN
Sau khi kết thúc OLP Tin Học SV, một số OLP-er quyết định đầu tư thuê đất để
trồng rau.
Mảnh đất thuê là một hình chữ nhật N x M (1<=N<=100; 1<=M<=100) ô đất hình
vuông. Nhưng chỉ sau đó vài ngày, trận lụt khủng khiếp đã diễn ra làm một số ô đất bị
ngập :(( Mảnh đất biến thành một số các ao.
Các OLP-er quyết định chuyển sang nuôi cá . Vấn đề lại nảy sinh, các OLP-er
muốn biết mảnh đất chia thành bao nhiêu cái ao để có thể tính toánnuôi trồng hợp lý. Bạn
hãy giúp các bạn ý nhé.
Chú ý: Ao là gồm một số ô đất bị ngập có chung đỉnh. Dễ nhận thấy là một ô đất
có thể có tối đa 8 ô chung đỉnh.
INPUT:
* Dòng1: 2 số nguyên cách nhau bởi dấu cách: N và M
* Dòng 2..N+1: M kí tự liên tiếp nhau mỗi dòng đại diện cho 1 hàng các ô đất.
Mỗi kí tự là ‘W’ hoặc ‘.’ tương ứng với ô đất đã bị ngập và ô đất vẫn còn nguyên.
OUTPUT:
* Dòng 1: 1 dòng chứa 1 số nguyên duy nhất là số ao tạo thành.

12


Input
10 12

Output
3

W……..WW.
.WWW…..WWW
….WW…WW.
………WW.
………W..
..W……W..
.W.W…..WW.
W.W.W…..W.
.W.W……W.
..W…….W.

13



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay

×