Chia sẻ một thuật toán hay
24/12/2005

Trong rất nhiều bài toán khi đọc lên yêu cầu ta đều có ngay thuật toán để giải quyết nó, song các thuật toán phát sinh đó nếu không phân tích kĩ và tinh tế nhận dạng thì chỉ giải quyết được khi kích cỡ dữ liệu đầu vào là khá nhỏ có nghĩa là chỉ giải quyết được bài toán trong những trường hợp rất hạn chế. Trong lớp các bài toán như vậy ở đây tôi xin giới thiệu một số bài toán khác nhau song cách giải quyết lại khá giống nhau.
Bài toán 1:

Cho file văn bản gồm rất nhiều dòng, mỗi dòng 80 kí tự. Các kí tự của file văn bản gồm “*” và “.” Các kí tự “*” trong văn bản tạo nên các chữ cái T,E,F,I. hãy đếm các số lượng các chữ cái T,E,F,I có trong văn bản giả sử dữ liệu vào là đúng.

Ví dụ file input như sau:

……….********…………*……..*************…………*********
……….*…………………..*…………….*…………………*…………..
……….*******....………..*…………….*………******….******…..
……….*………………….........................*…………*……..*…………..
……….*…...…………………………………………*……..**********

Thì có 2 chữ T,1 chữ E, 1 chữ F, và 1 chữ I.

Bài toán 2:

Bài toán tương tự mà tôi muốn giới thiệu ở đây là bài toán trong kì thi OLIMPIC Tin học Sinh viên Việt Nam lần thứ 14 năm 2005 tại TP.Hồ Chí Minh vừa qua. Đây là bài toán không khó, nhưng để vượt qua hết test của BGK lại là một điều không dễ. Bài toán đại ý như sau:

Ảnh chụp mặt người sau khi đã xử lý là 1 bảng vuông A kích thước NxN (10 < N < 800). Với mỗi ô (i,j) có giá trị từ 0 đến 255 là mức xám của ảnh tại ô này (trong đó 0 là màu nền). Để xác định vị trí là mặt người, ta cần thống kê các đặc trưng có dạng hình vuông kích thước k x k (1 < K ≤ 40) trong đó các giá trị trong hình vuông đều phải khác 0.

Yêu cầu : Từ 1 ảnh chụp mặt người đếm tất cả các đặc trưng có trong ảnh đó.

3. Cách giải quyết.

Với bài toán thứ nhất ta thấy rằng việc đọc dữ liệu vào một mảng 2 chiều là điều không thể, vì không biết trước số dòng của văn bản sẽ là bao nhiêu? bộ nhớ có đủ chỗ hay không? Mà nếu có đọc được đi chăng nữa thi chắc gì đã có cách nào hay để giải! Ta hãy đi tìm các đặc trưng của các chữ T,E,F,I. Ta thấy rằng các chữ cái này đều có 1 trục thẳng đứng. Cụ thể với chữ I có 1 trục thẳng không có gạch nằm ngang nào, với chữ T có 1 gạch nằm ngang, chữ F có 2 gạch nằm ngang, chữ E có 3 gạch… như vậy chữ I có 1 nét, chữ T có 2 nét, chữ F có 3 nét và chữ E có 4 nét. Ta sẽ đếm số nét của chữ để xác định đó là chữ gì!

Để đếm các nét của chữ ta sử dụng 1 mảng 1 chiều d[1..80], ban đầu ta clear các giá trị của mảng này=0, ta sử dụng 1 mảng dem[1..4] để đếm số lượng các chữ cái, trong đó dem[1] là số lượng chữ I, dem[2] là số lượng chữ T, dem[3] là số lượng chữ F và dem[4] là số lượng chữ E. Ta sẽ xử lý 2 dòng (st1,st2) liền nhau trong văn bản:

Ta xét các trường hợp sau:

1.(St1[i]=’*’^st1[i+1]=’*’^St2[i]=’*’^st2[i+1]=’.’)->d[i]:=d[i]+1
2.(St1[i-1]=’.’^St1[i]=’*’^st1[i+1]=’*’^St2[i]=’.’^d[i]<>0)->d[i]:=d[i]+2;
3.(St1[i-1]=’.’^St1[i]=’*’^st1[i+1]=’.’^St2[i]=’.’) -> d[i]:=d[i]+1

Trường hợp 2 và 3 là các trường hợp kết thúc của 1 chữ cái, do vậy ta sẽ tăng dem[d[i]] lên 1 trong các trường hợp đó, đồng thời d[i]:=0;Cứ như vậy cho đến khi kết thúc file và giá trị của dem[1..4] sẽ cho ta số lượng các chữ I,T,F,E đếm được. Phải chú ý rằng khi đọc hết file input thì st1 chứa dòng cuối cùng và st2 chứa 1 chuỗi các kí tự ‘.’ để xét lần cuối cùng. Như vậy bài toán 1 đã được giải quyết.

Với bài toán thứ 2: Cách đơn giản nhất mà ta nghĩ đến khi đọc đề bài có thể là cách áp tất cả các hình vuông KxK lên bảng vuông NxN rồi xét và đếm. cách tốt hơn nữa là trước khi áp hình vuông ta có thể đánh dấu các ô có giá trị 0 và các ô nằm trên cùng 1 hình vuông KxK với ô đó, và cách tốt hơn cách này nữa là hình vuông kế tiếp được xét bằng cách bớt cột bên trái thêm cột bên phải, hoặc bớt hàng bên trên thêm hàng bên dưới như vậy ta chỉ cần xét trên các cột thêm hoặc hàng thêm này mà thôi…song chúng ta phải chú ý rằng bài này đòi hỏi chạy trong 2 giây và dữ liệu tối đa là 800x800. Thực tế dữ liệu 800x800 đã là 1 gợi ý của bài toán để chúng ta biết rằng không thể đọc toàn bộ bảng vuông vào mảng 2 chiều được, nhưng như vậy lại có bạn đọc từng phần 1 để xử lý, tuy nhiên nếu thế lại không đáp ứng được thời gian chạy của chương trình.Và cách giải quyết ở đây là hoàn toàn đơn giản, chúng ta áp dụng như các giải quyết của bài TEFI ở trên.

Như vậy ta sẽ đọc từng dòng của bảng A vào chuỗi st và sử dụng mảng d[1..801] (phần tử thứ 801 dùng làm lính canh) kiểu word để xử lý. Giá trị d[i] sẽ là số các ô khác 0 liên tục tính từ dòng đang xét trở lên ở cột i của bảng A. Ban đầu ta clear các gía trị của mảng d[1..801].

//Khởi tạo ban đầu

Procedure Khoitao;
begin
Fillchar(a,sizeof(d),0);
dem:=0;
d[n+1]:=0;//phàn tử lính canh
end;
//Xử lý dữ liệu đọc vào
Procedure Xuly;
Var x,j,i : word;
Begin
Assign(f,’xulyanh.out’);
Reset(f);
For j:=1 to n do
Begin
For i:= 1 to n do
Read(f,x);
If x<>0 then inc(d[i])
Else d[i]:=0;
End;
If j>=k then Duyet;
readln(f);
End;
Close(f);
End;
//Xử lý mảng trên d được viết như sau:
Procedure Duyet;
Var j,i : word;
Begin
For i:= 1 to n-k do
Begin
J:=0;
While (d[i+j]>k ) and (j<=k) do inc(j);
If j>k then inc(dem);
End;
End;

Xử lý dữ liệu trên mảng d theo cách viết trên là chưa tối ưu, chỉ mang tính mô tả để ta dễ hiểu cách làm. Ta nên viết lại Procedure Duyet như sau:

Procedure Duyet;
Var j,i : word;
Begin
i:=1;
Repeat
While d[i] < k do inc(i);
if i < n-k then
Begin
j:=0;
While (d[i+j]>=k) and (j<=k) do inc(j);
if j>k then
Begin
inc(dem); i:=i+k+1;
while (d[i]>=k) do
Begin
inc(i);
inc(dem);
End;
End
Else i:=i+j;
End;
Until i>n-k
End;

Thoát khỏi vòng lặp này biến dem sẽ trả về số các đặc trưng tìm thấy của ảnh! Như vậy bài toán đã được giải quyết 1 cách trọn vẹn.

4. Bài toán mở rộng.

1. Cho file văn bản chứa các kí tự ‘0’ và ‘1’ file gồm rất nhiều dòng, mỗi dòng có 1000 kí tự. Hãy xác định toạ độ hình vuông lớn nhất trong văn bản chỉ chứa hoặc là kí tự ‘0’ hoặc là kí tự ‘1’. Yêu cầu đưa ra toạ độ đầu và độ dài của cạnh hình vuông tìm được.

2. Quay lại bài toán 1 nếu các chữ cái T,E,F,I của chúng ta có thể quay 900,1800, 2700 thì chúng ta sẽ đếm các chữ cái này như thế nào?

Xin hẹn các bạn ở một bài toán khác. Mọi góp ý, trao đổi xin liên hệ qua email duyhaman@yahoo.com.. Xin chân thành cảm ơn!

URL của bài viết này::http://www.vnschool.net/modules.php?name=News&file=article&sid=7