Các bài toán quen thuộc trong lập trình Pascal
Trang 1Cách nhìn khác đối với một số lớp bài toán quen thuộc
Cao Minh Anh
Các bạn đã bao giờ thích thú khi tìm ra một cách nhìn nhận mới cho những bài mình đã biết? Thật là thú vị nếu ta tìm cho mình một phương pháp khác mà tính tối ưu vẫn không giảm
Sau đây tôi sẽ trình bày với các bạn một cách nhìn nhận khác đối với lớp bài toán như xác định hoán vị thứ k, nhị phân thứ k, …
Tư tưởng thuật toán
- Giả sử cần tìm 1 trạng thái nào đó gồm n phần tử của một dạng xác định Đề bài cho biết đặc điểm của trạng thái trên là M
- Với M ta có thể xác định phần tử đầu tiên của trạng thái, nhưng n-1 phần tử còn lại thì không xác định được Vấn đề bây giờ là làm sao tìm được n-1 trạng thái còn lại Rất đơn giản, bởi vì nếu biết được M ta sẽ xác định được phần tử đầu tiên, tại sao ta không thử n-1 phần tử còn lại có phải thuộc một trạng thái nào đó gồm n-1 phần tử có đặc điểm M1 Từ M1 này ta suy được phần tử đầu tiên của trạng thái mới, tức là đã tìm được phần tử thứ 2 của trạng thái ban đầu Cứ làm như thế ta sẽ tìm được hết các phần tử của trạng thái ban đầu
- Cái khó của thuật toán trên là đòi hỏi người lập trình phải tìm được cách xác định phần tử đầu tiên dựa vào đặc điểm M, thứ hai là phải tìm được các đặc điểm M1, M2, , Mn-1 tương ứng với các trạng thái mới có ít phần tử hơn
Bài toán 1
Xác định dãy nhị phân thứ k gồm n phần tử (n≤200)
File nhiphan.inp
n k
File nhiphan.out
a1 a2 a3 an (dãy nhị phân thứ k)
Ví dụ
nhiphan.inp
4 7
nhiphan.out
0 1 1 0
Thuật toán
Trang 2Dựa vào ví dụ dưới ta nhận thấy rằng:
- Có 1 nửa trạng thái đầu tiên có số 0 đứng đầu, còn nửa còn lại là số 1 đứng đầu Hay có
2n-1 trạng thái đầu tiên bắt đầu là số 0, còn lại 2n-1 trạng thái bắt đầu là số 1 Như vậy chỉ cần so sánh k với 2n-1 thì ta có thể biết được phần tử đầu tiên của dãy nhị phân thứ k là 0 hay 1
If k>2n-1 then a[1]=1 else a[1]=0
- Vấn đề còn lại là làm sao xác định được n-1 phần tử còn lại
- Ta nhận thấy rằng nếu phần tử đầu tiên là 0 thì n-1 phần tử còn lại giống với các phần tử của dãy nhị phân thứ k có n-1 phần tử, còn nếu phần tử đầu tiên là 1 thì n-1 phần tử còn lại giống với các phần tử của dãy nhị phân thứ (k-2n-1) có n-1 phần tử Bây giờ ta lại làm bài toán nhỏ hơn là xác định dãy nhị phân thứ k’ nào đó gồm n-1 phần tử Cứ làm như thế ta sẽ tìm được hết các phần còn lại
Từ những nhận xét trên ta có chương trình đơn giản như sau:
For I:=n downto 1 do
Begin
If k>1 shl (I-1) then
Begin
Write(‘1’);
K:=k-(1 shl (I-1));
End
Else write(‘0’);
End;
Vì bài này dữ liệu lớn, ta chỉ cần làm thêm chương trình nhân số lớn để tính 2h, và hàm so sánh 2 xâu k với 2h
Bài 2
Trang 3Tìm hoán vị thứ k gồm n phần tử (n≤200)
Thuật toán
Nhận xét
- (n-1)! Trạng thái đầu tiên có phần tử đầu tiên là 1, tiếp theo (n-1)! Tiếp là 2, tiếp tới là 3,
và cuối cùng là 4
- Dựa vào k ta có thể xác định phần tử đầu tiên Nếu tìm được phần tử đầu tiên là h thì dùng mảng b để đánh dấu h, để n-1 phần tử còn lại không có h
- Bây giờ cần xác định vị trí k’ của n-1 phần tử còn lại
- K’ = k-v (với v là vị trí đầu tiên bắt đầu bằng số h) Việc tính v rất dễ dàng
Đây là phần giải tạm với dữ liệu k≤232
Fillchar(b,sizeof(b),1);
T:=1;
For I:=1 to n do T:=T*I;
For I:=n downto 1
Begin
T:=T div I;
For j:=1 to n do
If b[j] then
Begin
if K>T then k:=k-T
else
begin
write(j:4);
Trang 4b[j]:=false;
break;
end;
end;
end;
Ta có thể nâng dữ liệu lên n≤1000, chỉ cần làm chương trình nhân số lớn và hàm so sánh hai số lớn là xong
Bài 3
(Đề 2 - dãy nhị phân olympic tin học sinh viên khối chuyên tin)
Xét tập S các dãy nhị phân độ dài N trong đó không có hai bit 1 nào kề nhau Các dãy này được xếp theo chiều tăng dần của số nguyên mà nó biểu diễn, theo thứ tự đó mỗi dãy có một số hiệu, chẳng hạn n=5
Cho số nguyên dương N≤100 hãy nhị phân có số hiệu M
Ví dụ
BINSEQ.inp
5 5
BINSEQ.out
0 0 1 0 1
Thuật toán
Đây là một bài rất khó để ta xác định cách xác định phần tử đầu tiên
Trang 5Nhận xét :
Với n=5 có 13 dãy tất cả Trong đó có 8 dãy đầu tiên bắt đầu bằng 0,5 dãy còn lại bắt đầu bằng 1 Với n=6 thì có tất cả 21 dãy, 13 cái đầu là 0,8 cái sau là 1 Ta thấy nó có liên quan đến số fibonaci
Chỉ cần kiểm tra M với a[n-1], nếu M>a[n-1] thì đó là số 0 đầu tiên, ngược lại là số 1 đầu tiên Nếu số đầu tiên là 0 thì trạng thái mới cần tìm gồm n-1 phần tử có số hiệu vẫn là M, ngược lại có số hiệu là M-a[n-1]
Bài làm rất đơn giản như sau:
a[0]:=1;a[1]:=1;
For I:=2 to n do a[I]:=a[I-1]+a[I-2];(Tạo các số fibo)
For I:=n downto 1 do
Begin
If M>a[I-1] then
Begin
Write(‘1’);
M:=M-a[I-1];
End
Else write(‘0’);
End;
Thật là đơn giản phải không các bạn? Vấn đề ở chỗ phải tìm ra đặc điểm để xác định phần
tử đầu tiên và đặc điểm của các phần tử còn lại Hi vọng các bạn sẽ nhận thêm được kinh nghiệm nào đó từ bài viết này