Sáng kiến kinh nghiệm Kỹ thuật dùng bit trạng thái để xử lý hiệu quả bài toán Tin học

II. CƠ SỞ LÝ LUẬN

Qua nhiều năm giảng dạy, bồi dưỡng học sinh giỏi môn Tin học tôi nhận

thấy:

- Việc trang bị cho các em học sinh các kiến thức về thuật toán và giải quyết

vấn đề một cách khoa học là rất cần thiết.

- Phương pháp dùng kỹ thuật bit trạng thái để xử lý thực sự hiệu quả bài toán

tin học.

- Trong tất cả các kỳ thi chọn học sinh giỏi Tỉnh, các bài toán dùng phương

pháp bit trạng thái thực sự rất hiệu quả

III. CƠ SỞ THỰC TIỄN

Một số thuận lợi và khó khăn khi thực hiện chuyên đề sáng kiến kinh

nghiệm ở trường.

1. Thuận lợi:

- Môn Tin học là một môn học mới nhưng được sự ủng hộ của Cấp trên, Sở

giáo dục và Đào tạo, cấp Ủy, nhà trường đã tạo được điều kiện sắm sửa phòng máy

và các trang thiết bị cần thiết.

- Trường có cơ sở vật chất tốt.

- Học sinh rất hứng thú đối với môn học này

- Môn học có ứng dụng rất lớn trong thực tiễn nên học sinh rất chịu khó

nghiên cứu học tập.

- Hàng năm kết quả thi học sinh giỏi môn Tin đạt kết quả cao nên tạo động

lực lớn cho học sinh quyết tâm học tập.

2. Khó khăn:

- Các tài liệu bồi dưỡng học sinh giỏi môn tin còn rất ít.

- Giáo viên đủ chuẩn để bồi dưỡng học sinh giỏi môn tin còn hạn chế.

- Là môn học khó, đòi hỏi người học hội tụ quá nhiều phẩm chất như: Thông

minh, tư duy tốt, kiên trì, có năng khiếu về lập trình, cẩn thận, nhanh nhạnh nhưng

chính xác tuyệt đối trong từng kỹ thuật nhỏ.

pdf35 trang | Chia sẻ: thuydung3ka2 | Ngày: 04/03/2022 | Lượt xem: 1051 | Lượt tải: 3Download
Bạn đang xem 20 trang mẫu của tài liệu "Sáng kiến kinh nghiệm Kỹ thuật dùng bit trạng thái để xử lý hiệu quả bài toán Tin học", để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên
cout.tie(0); 
 freopen("FIRSTROW.inp","r",stdin); 
 freopen("FIRSTROW.out","w",stdout); 
 for(i=0;i<255;i++)a[i]=0; 
 while (getline(cin,s)) 
 { 
 for(i=0;i<s.length();i++) a[i]^=int(s[i]); 
 } 
for(i=0;i<255;i++)cout<<char(a[i]); 
} 
Bài toán 10. Dãy số 
Với số nguyên không âm X bất kì, bạn có thể tạo dãy số A[X] như sau: 
 Số đầu tiên của dãy là X. 
 Sau một số lẻ y sẽ là số y – 1. 
 Sau một số chẵn y sẽ là số y / 2. 
Ví dụ với X = 60. Ta có dãy số 60, 30, 15, 14, 7,  
Cho K, A, B nhiệm vụ của bạn là đếm xem có bao nhiêu số nguyên X trong 
đoạn từ A đến B mà dãy A[X] chứa K. 
Dữ liệu vào: Trong file văn bản NEXT.INP Gồm một dòng duy nhất ghi 3 
số K, A, B ( tất cả các số đều không âm và không quá 1018, A ≤ B). 
Kỹ thuật dùng bit trạng thái để xử lý hiệu quả bài toán Tin học 
Trang 22 
Kết quả ra: Ra file văn bản NEXT.OUT Ghi ra kết quả tìm được. 
Input Output 
3 4 8 2 
1 23457 123456 100000 
13 12345 67890123 8387584 
Lời giải: Nếu K = 0, kết quả là B – A + 1. 
 Gọi các số X mà trong A[X] có K là các số tạo K. 
 Trong trường hợp K >= 1. Các số tạo K sẽ có phần nhị phân đầu tiên giốn K 
hoặc K + 1 trong trường hợp K chẵn. 
 Ví dụ nếu K = 1012 thì các số tạo K phải có dạng 101x2 với x có thể rỗng 
hoặc là một xâu nhị phân. Nếu K chẵn, ví dụ K = 1002 thì các số tạo K có dạng 
100x2 hoặc 101x2 
 Ta sẽ đếm các số này theo số lượng bit. Giả sử K có L bít, như vậy các số 
tạo K có L bit sẽ nằm trong đoạn [C, D] với [C, D] bằng [K, K] nếu K lẻ và [K, K 
+ 1] nếu K chẵn. Các số tạo K có L + 1 bít sẽ nằm trong đoạn [C × 2, D × 2 + 1], 
các số tạo K có L + 2 bít sẽ nằm trong đoạn [C × 2 × 2, (D × 2 + 1) × 2 + 1]. Tìm 
giao của các đoạn này với đoạn [A, B] sẽ ra số lượng số cần tìm. 
#include 
using namespace std; 
#define bit(x, i) (x >> i) & 1 
void cass() 
{ 
 ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); 
 freopen("next.inp", "r", stdin); 
 freopen("next.out", "w", stdout); 
} 
long long k, a, b; 
int main() 
{ 
Kỹ thuật dùng bit trạng thái để xử lý hiệu quả bài toán Tin học 
Trang 23 
 cass(); 
 cin >> k >> a >> b; 
 if (k==0) {cout << b - a + 1; return 0;} 
 long long l, r; 
 if (k & 1) l = r = k; else { l = k; r = k + 1;} 
 long long res = 0; 
 int cs1, cs2; 
 for (int i = 0; i < 64; ++i) { 
 if (bit(k, i)) cs1 = i; 
 if (bit(b, i)) cs2 = i; 
 } 
 int x=cs2-cs1; 
 while (x>=0){ 
 long long tmp; 
 if (a > r || l > b) tmp = 0; 
 else { 
 long long mama = min(r, b), mimi = max(a, l); 
 tmp = max(mama - mimi + 1, 0LL); 
 } 
 res = res + tmp; 
 l = l * 2; r = r * 2 + 1; 
 x--; 
 } 
 cout << res; 
} 
Bài toán 11: Đầu bếp 
Một đầu bếp đang chuẩn bị một bữa ăn với N nguyên liệu (N < 20). Vị đầu 
bếp muốn bữa ăn phải có đầy đủ K chất (K < 20) C1, C2, , Ck. Để vấn đề dễ hiểu, 
Kỹ thuật dùng bit trạng thái để xử lý hiệu quả bài toán Tin học 
Trang 24 
ta chưa quan tâm đến lượng của các chất trong bữa ăn. Với mỗi nguyên liệu ta biết 
được nó có chứa chất nào trong thành phần của nó. Đầu bếp muốn biết tất cả các 
tập nguyên liệu mà tập đó chứa đầy đủ K chất. 
Dữ liệu: 
Dòng 1: N và K 
N dòng tiếp theo: mỗi dòng chứa K số thể hiện thành phần của mỗi nguyên 
liệu, số thứ i thể hiện nó có chứa chất Ci hay ko, là 1 nếu nó chứa chất Ci, 0 ngược 
lại. 
Chẳng hạn: 
6 7 
1 0 0 1 0 1 0 
0 1 0 0 1 0 0 
1 0 0 0 0 0 1 
0 0 1 1 0 0 0 
1 1 0 0 0 0 0 
0 0 0 0 0 1 0 
Đầu bếp có 6 nguyên liệu, và ông ta quan tâm bữa ăn phải chứa đủ 7 chất. 
Nguyên liệu thứ nhất chỉ chứa chất C1, C4, C6 
Giải quyết: 
Với mỗi nguyên liệu, nó sẽ có trạng thái là được chọn hoặc không được 
chọn. Mỗi nguyên liệu có 2 trạng thái, vậy sẽ có tất cả 2^N trạng thái cho N 
nguyên liệu. Với những trường hợp N nhỏ thì duyệt qua tất cả các trạng thái là 
chuyện dễ dàng (2^20 ~ 10^6). Cách duyệt qua tất cả các phương án để tìm lời giải 
gọi là duyệt trâu bò. Vấn đề là thể hiện cách duyệt qua tất cả trạng thái bằng ngôn 
ngữ lập trình. Bạn có thể dùng đệ qui để giải quyết, nó vẫn ok. Tuy nhiên, mình 
muốn trình bày bằng bitmask, code ngắn, dễ sử dụng lại. 
Do có 2^N trạng thái, tất cả trạng thái là 0000002=0, 0000012=1, 
0000102=2, , 1111112=2^N-1. Tất cả trạng thái là các số nguên chạy từ 0 đến 
2^N-1. Nên chỉ với một vòng lặp for(state=0; state < 2^N; state++) đã biểu diễn 
được việc “duyệt qua tất cả trạng thái“ 
Với mỗi giá trị của state, ta muốn biết nguyên liệu thứ i có được chọn hay 
ko, ta chỉ cần xét biểu thức (state and 2^i), i tính từ 0 đến K-1. Nếu biểu thức này 
Kỹ thuật dùng bit trạng thái để xử lý hiệu quả bài toán Tin học 
Trang 25 
lớn hơn 0 nghĩa là nguyên liệu thứ i được chọn trong trạng thái state, ngược lại thì 
0. and ở đây là phép and bit.(Bạn tự kiểm chứng biểu thức này). 
Một điểm chú ý nữa, Với mỗi nguyên liệu thứ i ta cũng chuyển thành một số 
b[i] để diễn tả thành phần của nó. Ví dụ với nguyên liệu thứ nhất, ta sẽ biểu diễn 
b[0] = 10010102=74. 
Như vậy nếu các nguyên liệu được chọn có các giá trị b[i] or với nhau hợp 
thành 11111112=(2^K-1) thì tập đó thỏa mãn. or ở đây là phép or bit 
For(state=0; state < 2^N; state++) 
{ 
 Sumarize = 0; 
 For(i=0; i 0) 
{ 
 Sumarize = Sumarize or b[i]; 
 } 
 If(Sumarize == (2^K-1)) 
{ 
 //Print the result 
 } 
} 
Độ phức tạp của đoạn code trên là O(2^N*K), với N=K=20 thì chạy trong 1s 
là ok. 
Bài toán 12: Biểu diễn trạng thái. 
Với bài toán 10, chúng ta chỉ biểu diễn không hoặc có. Thực tế kĩ thuật này 
được hiểu xa hơn một tí, ta xét ví dụ khá quen thuộc sau: 
Ta có ba chai nước có thể tích là V1, V2, V3 (Vi < 10) (đơn vị thể tích). Ban 
đầu ta có chai nước thứ nhất đầy nước và hai chai còn lại không chứa gì cả. Bạn 
cần tìm cách rót sao cho bình V3 chứa K (K<=V3) thể tích nước và số lần rót là ít 
nhất. Bạn được thực hiện một trong các cách sau: 
1. Rót hết nước từ chai này sang chai khác (được phép tràn) 
Kỹ thuật dùng bit trạng thái để xử lý hiệu quả bài toán Tin học 
Trang 26 
2. Rót nước từ chai này sang chai khác cho đến lúc chai khác được nhận đầy 
nước. 
3. Rót bỏ hết nước từ một chai. 
Với mỗi trạng thái (x1, x2, x3) thì ta cũng có thể dùng dãy 3 phần tử để diễn tả 
chúng. Tuy nhiên ta cũng có thể diễn tả trạng thái của ba chai bằng một số nguyên 
X= (x1*(V2+1)+x2)*(V3+1)+x3. Bạn có thắc mắc tại sao nhân với V2+1 và V3+1. 
Thật ra nhân với số lớn hơn 2 số này vẫn thỏa, hai số này là hai số nhỏ nhất có 
thể nhân vào, dựa vào tính chất của phép mod. Với cách diễn đạt này, thì đảm bảo 
với mỗi bộ số (x1, x2, x3) khác nhau thì X cũng khác nhau (bạn tự kiểm tra bằng 
phản chứng). Với mỗi số nguyên X biểu diễn cho một thái, ta có thể biết được 
lượng nước trong mỗi chai 
- x1 = X / ((V2+1)(V3+1)) 
- x2 = (X/(V3+1)) mod (V2 +1) 
- x3 = X mod (V3+1) 
Tổng cộng ta có (V1+1)*(V2+1)*(V3+1) trạng thái, ta cũng biết được quy tắc 
chuyển từ trạng thái này sang trạng thái khác thông qua các cách rót nước. 
Ta xây dựng một đồ thị có (V1+1)*(V2+1)*(V3+1) đỉnh, xây dựng các cạnh 
nối bằng các quy tắc của đề bài. Trạng thái bắt đầu là (V1, 0 ,0), số nguyên biểu 
diễn là V1*(V2+1)*(V3+1). Trạng thái kết thúc là (0, 0, K), số nguyên biểu diễn là 
K. 
Có đồ thị, việc tìm đường đi ngắn nhất từ đỉnh này đến đỉnh khác là dễ dàng 
thông qua các giải thuật có sẵn. Với bài này ta có thể dùng breadth first search 
hoặc dijkstra. Ở đây ta không chú tâm đến việc giải bài này, ta chỉ tìm hiểu cách 
tiếp cận biểu diễn trạng thái bằng một số nguyên. 
Bài toán 13: Quy hoạch động 
Bài toán tìm đường đi Hamilton là một bài toán khá quen thuộc: cho N đỉnh, 
tìm đường đi sao cho mỗi đỉnh được thăm duy nhất một lần, và chi phí thăm N 
đỉnh là thấp nhất. 
Với bài toán này, thông thường nếu N nhỏ (N <=10), ta có thể giải quyết bài 
toán bằng phương pháp nhánh cận. Tuỳ vào cách đặt cận mà tốc độ chương trình 
có thể được nâng lên, tuy nhiên thời gian chạy chương trình vẫn có thể không đảm 
bảo với một số test khó. 
Kỹ thuật dùng bit trạng thái để xử lý hiệu quả bài toán Tin học 
Trang 27 
Không những vậy, nếu giới hạn N tăng lên đến 20, thì việc sử dụng phương 
pháp nhánh cận để giải quyết sẽ không còn khả thi nữa. Trong trường hợp này, ta 
có cách tiếp cận khác để giải quyết bài toán một cách hiệu quả hơn, với độ phức 
tạp O(N^2*2^N). Đó chính là sử dụng quy hoạch động và bitmask (cách thường 
gọi là quy hoạch động trạng thái). 
Ý tưởng 
Để giải quyết bài toán này theo phương pháp quy hoạch động, các buớc thiết 
lập lời giải phải được lưu trữ để có thể tính toán từ các bài toán con. Rõ ràng ở đây, 
chúng ta phải lưu các đỉnh đã đi qua và đỉnh cuối cùng của một hành trình bất kỳ. 
Để làm việc này, ta dùng một mảng nhị phân N phần tử với ý nghĩa bit 1 tượng 
trưng cho các đỉnh đã được thăm và ngược lại. Từ đó, ta định nghĩa Fx [i,j] là chi 
phí tối ưu trong trạng thái i, j là đỉnh kết thúc hành trình (0 <= i <= 2^N -1; 1 <= j 
<= N). 
Ví dụ: 
Dãy [010011]2 tương đương trạng thái có 3 đỉnh được đi, và đỉnh kết thúc 
hành trình là đỉnh 5 (tính từ phải qua, bạn có thể lưu từ phải qua, vấn đề này không 
ảnh hưởng gì), với biểu diễn trong hệ thập phân tương ứng của dãy này là 19. Vậy 
hàm quy hoạch động của trạng thái này là Fx[19,5] 
Chi tiết thuật toán 
Với mỗi trạng thái I có j là đỉnh đến cuối cùng trong trạng thái này, xét mọi 
đỉnh k khác j mà k chưa được thăm trong trạng thái I. Gọi trạng thái mà hành trình 
của nó có chứa đỉnh k là X. 
Ví dụ: 
I = [010011] có các đỉnh 1,2,5 đã thăm. Giả sử k = 3 (thoả vì chưa đến được 
thành phố 3 trong trạng thái I), ta được trạng thái X = [010111]. 
Tóm lại, ở bước này: trạng thái X và trạng thái I chỉ khác nhau duy nhất ở 
đỉnh k. Dễ nhận thấy số thành phố đi được thấy đi được ở trạng thái X = số thành 
phố đi được ở trạng thái I + 1, suy ra việc tính toán Fx[X,k] sẽ dựa vào kết quả bài 
toán có kích thước nhỏ hơn nó là Fx[I,j] 
Tiếp theo, ta sẽ tối ưu Fx[X,k] theo công thức : Fx[X,k] = Min (Fx[X,k], 
Fx[I,j] + C[j,k]); 
Công thức trên sẽ lấy chi phí nhỏ hơn giữa trạng thái hiện tại (hành trình kết 
thúc tại k) và trạng thái hành trình kết thúc tại j cộng với chi phí di chuyển từ j -> 
k. 
Kỹ thuật dùng bit trạng thái để xử lý hiệu quả bài toán Tin học 
Trang 28 
Bước tìm đường đi có chi phí ngắn nhất chính là giá trị nhỏ nhất trong tất cả 
đường đi qua N đỉnh mà đường đi đó kết thúc tại thành phố i. Từ đó ta có kết quả 
bài toán là Min (Fx [2^N - 1, i]) với i =1..N. 
Số 2^N-1 thể hiện trạng thái mà N đỉnh đã được thăm. Ví dụ: 2^6 - 1 = 6310 
= 1111112 
Như đã đề cập ở trên, độ phức tạp cỡ khoảng 2^N*(N^2), dù sao cũng nhỏ 
hơn N! 
Bài toán 14: Chọn ô 
Cho một bảng hình chữ nhật kích thước 4×n ô vuông. Các dòng được 
đánh số từ 1 đến 4, từ trên xuống dưới, các cột được đánh số từ 1 đến n từ trái qua 
phải. Ô nằm trên giao của dòng i và cột j được gọi là ô (i,j). Trên mỗi ô (i,j) có ghi 
một số nguyên aij , i =1, 2, 3, 4; j =1, 2, ..., n. Một cách chọn ô là việc xác định một 
tập con khác rỗng S của tập tất cả các ô của bảng sao cho không có hai ô nào trong 
S có chung cạnh. Các ô trong tập S được gọi là ô được chọn, tổng các số trong các 
ô được chọn được gọi là trọng lượng của cách chọn. 
Tìm cách chọn sao cho trọng lượng là lớn nhất. 
Ví dụ: Xét bảng với n=3 trong hình vẽ dưới đây: 
Cách chọn cần tìm là tập các ô S = {(3,1), (1,2), (4,2), (3,3)} với trọng 
lượng 32. 
Dữ liệu vào: Trong file văn bản SELECT.INP 
Dòng đầu tiên chứa số nguyên dương n là số cột của bảng. 
Cột thứ j trong số n cột tiếp theo chứa 4 số nguyên a1j, a2j, a3j, a4j, hai số 
liên tiếp cách nhau ít nhất một dấu cách, là 4 số trên cột j của bảng. 
Kết quả ra: Ra file văn bản SELECT.OUT 
Gồm 1 dòng duy nhất là trọng lượng của cách chọn tìm được. 
Kỹ thuật dùng bit trạng thái để xử lý hiệu quả bài toán Tin học 
Trang 29 
Ví dụ: 
SELECT.INP SELECT.OUT 
3 32 
-1 9 3 
-4 5 -6 
7 8 9 
9 7 2 
Hạn chế 
Trong tất cả các test: n ≤ 10000, |aij| ≤ 30000. Có 50% số lượng test với n ≤ 
1000. 
Tư tưởng chính: 
- Mỗi cột có 4 dòng nên ta có thể dùng dãy nhị phân gồm 4 bít để biểu diễn. 
Tùy vào giá trị của bít thứ k của cột thứ i bằng 0 hoặc1 cho biết dòng thứ k của cột 
thứ i có được chọn hay không. (0 ≤ k ≤ 3) 
- Dãy nhị phân có 4 bít ứng với 24 trạng thái, biểu diễn từ 0 tới 24-1. 
- Gọi F[i,x] là tổng trọng lượng lớn nhất xét từ cột thứ 1 tới cột thứ i và trạng 
thái chọn của cột thứ i được biểu diễn bằng biến x. 
- Ta có công thức quy hoạch động như sau: 
F[i,x]=max(F[i-1,y])+sum(i,x) 
Trong đó: 
- Biến x và y là hai trạng thái tương ứng của cột thứ i và i-1. Theo yêu cầu 
của bài toán thì trạng thái x và y phải thỏa mãn bít thứ k của biến x phải khác bít 
thứ k của biến y vì việc chọn ô phải thỏa mãn không có hai ô nào có chung 
cạnh.(Getbit(k,x)Getbit(k,y)) 
- Hàm Sum(i,x) là trọng số của cột thứ i tương ứng với trạng thái x. 
Cài đặt: 
Biến A chứa giá trị của bảng, biến m =24-1 với m+1 là số trạng thái 
Trong chương trình sử dụng một số hàm như sau: 
Kỹ thuật dùng bit trạng thái để xử lý hiệu quả bài toán Tin học 
Trang 30 
+) Lấy giá trị bít thứ k của trạng thái x: Getbit(k,x) 
Function Getbit(k,x:word):byte; 
Begin 
Getbit:=(x shr k)and 1; 
End; 
+) Hàm kiểm tra trạng thái x có thỏa mãn yêu cầu bài toán không (không có 
hai ô nào có chung cạnh), nghĩa là bít thứ k và bít thứ k-1của trạng thái x phải 
khác nhau: 
Function Ok(x:word):boolean; 
Begin 
Ok:=false; 
For k:=1 to 3 do 
If (getbit(k-1,x)=1)and(getbit(k,x)=1) then Exit; 
Ok:=true; 
End; 
+) Hàm kiểm tra trạng thái x và trạng thái y có thỏa mãn yêu cầu bài toán 
không (không có hai ô nào có chung cạnh), nghĩa là bít thứ k của trạng thái x và 
bít thứ k-1 của trạng thái y phải khác nhau: 
Function Check(x,y:word):boolean; 
Begin 
Check:=false; 
For k:=0 to 3 do 
If (getbit(k,x)=1)and(getbit(k,y)=1) then Exit; 
Check:=true; 
End; 
+) Tính trọng lượng của cột thứ i ứng với trạng thái x: 
Function sum(i,x:word):longint; 
Var T:longint; 
Begin 
T:=0; 
Kỹ thuật dùng bit trạng thái để xử lý hiệu quả bài toán Tin học 
Trang 31 
For k:=0 to 3 do 
If (getbit(k,x)=1) then T:= T+A[k+1,i]; 
Sum:=T; 
End; 
+) Thủ tục chính: 
Procedure process; Var max:longint; 
y,i:word; 
Begin 
For i:=1 to n do {Xét lần lượt n cột} 
For x:=0 to m do {Với mỗi cột i xét 24 trạng thái} 
If Ok(x) then {Nếu trạng thái x thỏa mãn bài toán} 
Begin 
max:=-vc; 
{Tìm trạng thái y của cột thứ i-1 có tổng trọng lượng lớn nhất} 
For y:=1 to m do 
If check(x,y)and(ok(y))then If 
max<F[i-1,y] then 
max:=F[i-1,y]; 
F[i,x]:=max+sum(i,x) 
End; 
End; 
+) Thủ tục in kết quả: 
Procedure printresult; 
Var Max:longint; 
 ff:text; 
Begin 
{Chú ý thêm trường hợp tất cả A[i,j]<0} 
{Tìm giá trị lớn nhất trong các trạng thái của cột thứ n} 
Kỹ thuật dùng bit trạng thái để xử lý hiệu quả bài toán Tin học 
Trang 32 
Max:=-vc; 
For x:=0 to m do 
If ok(x)and (max<F[n,x]) then Max:=F[n,x]; 
Assign(ff,fo); rewrite(ff); Writeln(ff,max); 
Close(ff); 
End; 
Nhận xét 
 Độ phức tạp thuật toán: O(24*24*N) đủ để giải quyết bài toán trên. 
Để giảm độ phức tạp của thuật toán ta có thể dùng 1 mảng B lưu những 
trạng thái thỏa mãn yêu cầu không có hai ô nào cùng cột có chung cạnh kết hợp 
với cấu trúc dữ liệu Foward Star để ứng với mỗi trạng thái x ta có danh sách các 
trạng thái y thỏa mãn. 
Bài toán 15: Chuyến du lịch - TRIP 
Trong kì nghỉ hè năm nay Sherry được bố thưởng cho 1 Tour du lịch quanh 
N đất nước tươi đẹp với nhiều thắng cảnh nổi tiếng (vì sherry rất ngoan). Tất nhiên 
sherry sẽ đi bằng máy bay. 
Giá vé máy bay từ đất nước i đến đất nước j là Cij (dĩ nhiên Cij có thể khác 
Cji ). Tuy được bố thưởng cho nhiều tiền để đi du lịch nhưng Sherry cũng muốn 
tìm cho mình 1 hành trình với chi phí rẻ nhất có thể để dành tiền mua quà về tặng 
mọi người (Các chuyến bay của Sherry đều được đảm bảo an toàn tuyệt đối). 
Bạn hãy giúp Sherry tìm 1 hành trình đi qua tất cả các nước, mỗi nước đúng 
1 lần sao cho chi phí là bé nhất. 
Dữ liệu vào: Trong file văn bản TRIP.INP 
Dòng 1: N (5 < N < 16) 
Dòng thứ i trong N dòng tiếp theo: Gồm N số nguyên, số thứ j là Cij (0 < Cij 
< 10001) 
Kết quả ra: Ra file văn bản TRIP.OUT 
Gồm 1 dòng duy nhất ghi chi phí bé nhất tìm được 
Ví dụ 
TRIP.INP TRIP.OUT 
Kỹ thuật dùng bit trạng thái để xử lý hiệu quả bài toán Tin học 
Trang 33 
6 8 
0 1 2 1 3 4 
5 0 3 2 3 4 
4 1 0 2 1 2 
4 2 5 0 4 3 
2 5 3 5 0 2 
5 4 3 3 1 0 
Hướng dẫn: 
- Ta nhận thấy đây là bài toán tìm đường đi Hamilton, một bài toán khá quen 
thuộc: cho N đỉnh, tìm đường đi sao cho mỗi đỉnh được thăm duy nhất một lần, và 
chi phí thăm N đỉnh là thấp nhất. 
- Với dạng toán này, thông thường nếu N nhỏ (≤10), ta có thể giải quyết bài 
toán bằng phương pháp nhánh cận. Nhưng nếu giới hạn N tăng lên đến 20, thì việc 
sử dụng phương pháp nhánh cận để giải quyết sẽ không còn khả thi nữa. Trong 
trường hợp này, ta có cách tiếp cận khác để giải quyết bài toán một cách tối ưu 
hơn, với độ phức tạp O(N2*2N). 
- Dãy nhị phân có N bít ứng với 2n trạng thái, biểu diễn từ 0 tới 2n-1 cho biết 
các đỉnh đã được thăm hay chưa. 
- Gọi F[i,x] là tổng chi phí nếu sherry đang ở Đất nước i và trạng thái các 
nước đã đi qua được lưu vào biến. 
- Công thức Quy hoạch động: 
F[i,x] = min( F[j,y] + C[j,i] ) 
Trong Đó 
- j là đất nước đã được đánh dấu đi qua trong x. 
- y là trạng thái giống như trạng thái x nhưng đất nước i chưa được đánh dấu. 
Bài toán 16: Cô gái chăn bò - COWGIRL 
Trên một thảo nguyên nhỏ bé có 1 gia đình gồm 3 anh em: 2 người anh trai 
là Nvutri và Andorea còn người em gái là Lola. Cuộc sống gia đình khá giả nhưng 
Kỹ thuật dùng bit trạng thái để xử lý hiệu quả bài toán Tin học 
Trang 34 
gia đình có truyền thống chăn nuôi và muốn để các con tự lập nên cha mẹ 3 người 
quyết định để các con hằng ngày sẽ đi chăn 1 số bò nào đó (tùy ý 3 người con). 
Thảo nguyên là 1 cánh đồng chia làm M*N ô vuông, mỗi con bò chỉ đứng 
trong 1 ô và mỗi ô chỉ chứa 1 con bò.Chỉ có 1 quy tắc duy nhất là không bao giờ 
được để 4 con bò tạo thành 1 hình vuông 2*2 hoặc để trống 1 khu đất 2*2. 
Hai người anh mải chơi nên đã hối lộ kem để Lola chăn bò 1 mình. Lola 
muốn biết tất cả có bao nhiêu cách xếp bò thỏa mãn quy tắc trên để đề phòng mọi 
trường hợp. Vì con số này rất lớn nên hãy giúp Lola tính toán con số này. 
Dữ liệu vào: Trong file văn bản COWGIRL.INP 
Dòng đầu gồm 1 số T duy nhất là số test (T ≤ 111) 
T dòng tiếp theo gồm 2 số M, N cho biết kích thước của thảo nguyên (M*N 
≤ 30) 
Kết quả ra: Ra file văn bản COWGIRL.OUT 
Gồm T dòng, mỗi dòng ứng với 1 test là số cách xếp bò của test đó. 
Ví dụ: 
COWGIRL.INP COWGIRL.OUT 
1 1 
1 
2 
Hướng dẫn: 
- Ta thấy M*N<=30 nên tồn tại một số M hay N không lớn hơn 5. 
- Giả sử là M là số dòng không quá 5. Ta có thể sử dụng số có không quá 5 
bit nhị phân Để mô tả trạng thái chọn ở mỗi cột. 
- Gọi F[i,x] là số cách xếp bò thỏa mãn yêu cầu bài toán xét từ cột thứ 1 tới 
cột thứ i và trạng thái chọn của cột thứ i được biểu diễn bằng biến x.. 
- Công thức Quy hoạch động: 
F[i,x] = F[i,x] + F[i-1,y] ) 
Kỹ thuật dùng bit trạng thái để xử lý hiệu quả bài toán Tin học 
Trang 35 
C. KẾT LUẬN 
Đề tài này mang tính chất thời sự và cấp thiết trong việc trang bị cho các em 
học sinh giỏi tin học về cách tìm thuật toán cho bài toán là một điều hết sức cần 
thiết. 
Các giáo viên bồi dưỡng học sinh giỏi cần tìm, và thu thập các thuật toán 
giải các bài toán theo dạng từ dễ đến khó là phương pháp dạy lập trình đặc biệt có 
hiệu quả. Chính vì thế truyền đạt cho các em kiến thức để các em tiếp cận được các 
thuật toán là một điều không thể thiếu đối với các giáo viên và các em được bồi 
dưỡng thi học sinh giỏi. 
Vì khuôn khổ bài viết, vì vốn kiến thức còn hạn hẹp, tôi rất mong nhận được 
các ý kiến trao đổi, góp ý của đồng nghiệp. 
Tôi xin chân thành cảm ơn . 
Hưng Nguyên, ngày 03/03/2021 
 TÁC GIẢ 
 Phan Văn Thế 

File đính kèm:

  • pdfsang_kien_kinh_nghiem_ky_thuat_dung_bit_trang_thai_de_xu_ly.pdf
Sáng Kiến Liên Quan