Professional Documents
Culture Documents
Tác giả: Hà Đại Tôn, Trường THPT Chuyên Hạ Long – Quảng Ninh
A. MỞ ĐẦU
Trie tree hay còn gọi là cây tiền tố, được sử dụng rất nhiều trong các bài toán so sánh chuỗi.
Định nghĩa đơn giản và dễ hiểu nhưng nó có ứng dụng khá rộng.
Bài toán: Cho tập S gồm N xâu 𝑆1 , 𝑆2 , … , 𝑆𝑁 và tập 𝑋 gồm 𝑀 xâu 𝑋1 , 𝑋2 , … , 𝑋𝑀 . Các xâu có độ
dài không vượt quá 𝐿. Hãy kiểm tra xem mỗi xâu thuộc tập X có xuất hiện trong tập 𝑆 hay
không?
Nhận xét: Ta có thể giải bài toán này bằng cách duyệt tất cả các xâu thuộc S để kiểm tra với từng
xâu X. Độ phức tạp của thuật toán nàylà khoảng 𝑂(𝑁. 𝑀. 𝐿). Thuật toán này chỉ có thể chạy
được với các bộ dữ liệu nhỏ. Sau đây chúng ta sẽ xét một cấu trúc dữ liệu rất hiệu quả để giải bài
toán này với độ phức tạp 𝑂(𝑚𝑎𝑥(𝑀, 𝑁). 𝐿). Đó là cây tiền tố Trie Tree.
B. NỘI DUNG
2. Cấu trúc dữ liệu Trie Tree
a) Cách xây dựng Trie Tree:
Để hiểu rõ về cấu trúc cũng như cách xây dựng cây Trie ta sẽ xét một ví dụ cụ thể. Giả sử
ta có xâu S = ‘abc’. Cây trie bắt đầu được xây dựng như sau:
- Lúc đầu khởi tạo cây Trie chỉ có 1 nút duy nhất và ta sẽ bắt đầu xây dựng cây từ nút này
0 chỉ có khả năng là 010 -> 1 tin nhắn. 1 chỉ có khả năng là 1, 100, hoặc 110 -> 3 tin
nhắn. 01 chỉ có thể là 010 -> 1 tin nhắn. 01001 chỉ có thể là 010 -> 1 tin nhắn. 11 chỉ có
thể là 1 hoặc 110 -> 2 tin nhắn.
Hướng dẫn: Trước tiên ta sẽ Insert tất cả các xâu trong tập M xâu thu được từ vào Trie Tree
Ta sẽ sử dụng 2 mảng như sau:
sl[p] là số lượng các từ kết thúc tại nút p
sl2[p] là số lượng các từ đi qua nút p
Với mỗi từ j trong tập N xâu, ta sẽ làm như sau:
Khởi tạo số lượng từ có thể mã hóa được từ xâu j: cnt== 0
Duyệt trên cây Trie, với mỗi đỉnh p trong quá trình duyệt ta sẽ cộng kết quả thêm lượng là
sl[p]: cnt+= sl[p]. Đây chính là bước tính số lượng các từ có chiều dài ≤ chiều dài từ j mà
mã hóa được từ j
Trong quá trình duyệt, nếu ta không thể đi được hết xâu j thì ngay lập tức đưa ra kết quả
là cnt
Nếu xâu j có thể đi được hết trên Trie Tree, ta cần phải đếm thêm các từ có độ dài lớn hơn độ
dài xâu j mà có phần đầu trùng với xâu j, số lượng các xâu này chính là: sl2[p]- sl[p] với p là
nút cuối cùng trong quá trình duyệt xâu j trên Trie Tree.
Test và solution: http://www.mediafire.com/download/wthl1f8ctm9520f
Bài 4. Andrew và Alex là những nhà sáng tạo trò chơi, ngày hôm nay họ sáng tạo ra trò
chơi mới với xâu dành cho hai người
Giả sử ta có một tập hợp gồm n xâu khác rỗng. Với mỗi lượt chơi, 2 người chơi cùng nhau
xây dựng 1 xâu, lúc đầu xâu được khởi tạo rỗng. Mỗi người chơi sẽ đi theo lượt, đến lượt
của mình, người chơi phải thêm vào cuối xâu một kí tự duy nhất sao cho xâu mới tạo thành
là tiền tố của ít nhất 1 xâu trong tập hợp n xâu ban đầu. Một người sẽ thua nếu như người
đó không thể thực hiện được việc thêm kí tự vào xâu.
Andrew và Alex quyết định sẽ chơi k ván. Người thua cuộc ở ván thứ i sẽ là người đi trước
trong ván thứ (i+1). Người chiến thắng chung cuộc sẽ là người thắng ở ván đấu thứ k (tức
là ván đấu cuối cùng) chứ không phải là người thắng nhiều ván hơn. Giả sử nếu hai người
đều chơi theo chiến thuật tối ưu thì sau k ván ai sẽ là người chiến thắng?
Input:
- Dòng đầu tiên chứa 2 số nguyên n và k (1 ≤ n ≤ 105; 1 ≤ k ≤ 109).
- Mỗi dòng trong số n dòng tiếp theo, mỗi dòng chứa một xâu khác rỗng nằm trong
tập hợp xâu ban đầu. Tổng số kí tự của tất cả các xâu trong tập hợp không vượt quá
105, xâu chỉ bao gồm các chữ cái tiếng Anh in thường
Output:
- Gồm một dòng duy nhất: ghi ra ‘First’ nếu người đi trước ở ván đầu tiên chiến thắng
chung cuộc, ghi ra ‘Second’ trong trường hợp người đi sau ở ván đầu tiên chiến
thắng chung cuộc
Example:
Input Output
23 First
a
b
31 First
a
b
c
12 Second
Ab
Hướng dẫn:
Đây là một bài toán trò chơi. Một trong những kĩ thuật cơ bản nhất để giải các bài toán trò chơi
đó là xác định các vị trí thắng và vị trí thua trong bài toán. Và điểm mấu chốt của bài toán này
chính là xác định các vị trí đó.
Trước hết, ta sẽ xây dựng cây tiền tố Trie cho tập gồm n xâu ban đầu. Sau khi đã có cây Trie, ta
mô tả lại các bước đi của trò chơi như sau: lúc đầu người đi trước sẽ đứng ở đỉnh 1 của cây Trie,
sau đó đi theo 1 cạnh của cây để đến 1 đỉnh khác, sau đó người thứ 2 lại tiếp tục như vậy, đến khi
nào một người chơi đi đến một nút lá trên cây, tức là người sau không thể đi tiếp được nữa thì
người đó sẽ là người chiến thắng của ván đấu.
Đến đây ta sẽ định nghĩa vị trí thắng, vị trí thua như sau: “ Một nút trên cây là vị trị có thể thắng
nếu người chơi đứng ở nút đó với cách chơi tối ưu luôn dành chiến thắng, một nút trên cây là vị
trí có thể thua nếu người chơi đứng ở nút đó, với cách chơi của mình sẽ luôn thua “. Vậy tại sao
ta lại dùng từ “có thể”? Bởi vì người chiến thắng là người thắng được ván cuối cùng chứ không
phải là người thắng nhiều ván hơn, vậy nên có thể có người chơi CỐ TÌNH THUA ở một số ván
để giành được chiến thắng ở ván cuối cùng. Ta có thêm 1 số định nghĩa như sau: “ Một nút là nút
thắng HOÀN TOÀN, nếu từ nút đó ta chỉ có thể đi được đến các nút thua HOÀN TOÀN. Một nút
là nút thua HOÀN TOÀN nếu từ nút đó ta chỉ có thể đi được đến các nút thắng HOÀN TOÀN. Một
nút là nút tiềm năng nếu từ đó ta có thể đến được cả nút THẮNG HOÀN TOÀN và THUA HOÀN
TOÀN. Một nút là nút không xác định nếu từ nút đó ta chỉ đến được các nút tiềm năng”. Như vậy
với mỗi nút trên cây Trie, ngoài mảng next, ta có thêm 2 biến boolean là win(W), lose(L). Nếu
(W== True && L== False) thì nút là thắng HOÀN TOÀN, nếu (W==F && L== T) thì nút là
thua HOÀN TOÀN, nếu (W== T && L== F) thì nút là tiềm năng, đặc điểm của nút không xác
định ta sẽ xét phía dưới ( W= win, L= lose, T= true, F= false)
Như vậy, từ những định nghĩa ban đầu như trên ta thấy rằng, mọi nút lá của Trie đều là nút thua
HOÀN TOÀN. Bây giờ ta cần tiến hành xây dựng trạng thái cho tất cả các nút. Ở đây ta sử dụng
quy hoạch động trên cây như sau: Nếu nút u có thuộc tính (W== F) thì nút cha trực tiếp của u
phải có thuộc tính (W== T). Nếu nút u có thuộc tính (L==F) thì nút cha trực tiếp của u phải có
thuộc tính (L==T). Lúc đầu các nút đều có trạng thái là (W== F && L== F) , trừ nút lá có
(W==F && L==T). Như đã nói ở phần trên, nút không xác định là nút mà chỉ đi đến được các
nút tiềm năng, theo cách qhđ của ta như trên thì nút không xác định sẽ có thuộc tính là (W==F
&& L== F), và cũng theo cách qhđ trên thì cha trực tiếp của nút không xác định chính là nút tiềm
năng.
Sau khi thực hiện xong quy hoạch động ta sẽ tìm kết quả của bài toán:
- Nếu nút 1 là nút thua HOÀN TOÀN thì kết quả rõ ràng là người thứ 2 luôn thắng (vì người
thua lại phải đi trước ở ván sau)
- Nếu nút 1 là nút thắng HOÀN TOÀN thì nếu số ván là lẻ thì người 1 sẽ thắng, ngược lại
người 2 sẽ thắng
- Nếu nút 1 là nút tiềm năng thì người 1 luôn luôn thắng, vì nút 1 là tiềm năng tức là nó có
thể đến được nút thua hoàn toàn hoặc thắng hoàn toàn nên chiến thuật của người 1 sẽ là:
k-1 ván đầu tiên đi vào nút thắng HOÀN TOÀN để người 2 thắng ván đó, đến ván thứ k thì
người 1 sẽ đi vào nút thua HOÀN TOÀN và như vậy người 2 sẽ thua ván cuối cùng. Còn
một trường hợp nữa là trong các con của nút 1 có nút không xác định, thì người 1 chỉ cần
đi vào nút không xác định này, đến bước tiếp theo người 2 sẽ buộc phải đi vào 1 nút tiềm
năng và người 1 lại áp dụng chiến thuật như trên (do ta vừa nói ở trên, nút không xác định
chỉ đến được các nút tiềm năng)
- Nếu nút 1 là nút không xác định thì người 2 luôn thắng, bởi lẽ từ nút không xác định chỉ
có thể đi đến được các nút tiềm năng, và cũng như ở trường hợp trên, lần này người thứ 2
sẽ thắng.
Nhận xét: Đây là một bài tập khó đối với các bạn mới lần đầu tiếp xúc với bài toán trò chơi trong
tin học. Vậy kinh nghiệm để giải các bài toán dạng này đó chính là sử dụng thuật toán quy hoạch
động trên tập hợp các vị trí thắng và thua, từ đó tìm ra kết quả bài toán
Test và solution: http://www.mediafire.com/download/plse3vdflx6ogds
Bài 5. Chuỗi từ có độ dài n là một dãy các từ w1, w2, ..., wn sao cho với mọi 1 ≤i < n, từ
wi là tiền tố của từ wi+1. Nhắc lại từ u có độ dài k là tiền tố của từ v có độ dài l nếu l > k
và các ký tự đầu tiên của v trùng với từ u. Cho tập hợp các từ S = {s1, s2, ..., sm}. Tìm
chuỗi từ dài nhất có thể xây dựng được bằng cách dùng các từ trong tập hợp S (có thể
không sử dụng hết các từ).
Input
Dòng đầu tiên chứa số nguyên m (1 ≤m ≤250000). Mỗi dòng trong số m dòng sau
chứa một từ trong tập S. Biết rằng mỗi từ có độ dài không quá 250000 ký tự và tổng độ dài
của các từ không vượt quá 250000 ký tự.
Output
In ra một số duy nhất là độ dài của chuỗi từ dài nhất xây dựng được từ các từ trong
tập đã cho.
Example:
Input Output
3 3
a
ab
abc
5 2
a
ab
bc
bcd
add
Test và solution: http://www.mediafire.com/download/p27d3jao0tvee0f
C. THẢO LUẬN VÀ KẾT LUẬN
Người ta đã chứng minh được rằng số lượng nút trên cây Trie Tree không vượt quá 4*L
với L là tổng số kí tự của các xâu mà ta thực hiện thao tác Insert. Vì vậy trong các bài toán về
xâu, nếu muốn sử dụng Trie Tree cần phải chú ý đến giới hạn này. Bởi vì khi L quá lớn, ta sẽ
không đủ bộ nhớ để lưu Trie Tree, những bài toán như vậy ta không nên sử dụng Trie. Cũng cần
lưu ý thêm rằng, 4L là số lượng tính toán trên lý thuyết, trên thực tế các bài toán ta chỉ cần cung
cấp bộ nhớ L là đủ.
Tôi đã trình bày một cấu trúc dữ liệu đặc biệt, TRIE TREE. Cây tiền tố được định nghĩa và
cài đặt không quá phức tạp. Tuy nhiên, cây tiền tố lại có ứng dụng rộng rãi trong lĩnh vực khoa
học máy tính nói chung và trong các bài thi ôn thi VNOI nói riêng. Một số bài toán kinh điển ứng
dụng của TRIE TREE ví dụ: Bài toán tìm kiếm xâu, xâu con lặp dài nhất, xâu con chung dài nhất,
xâu đối xứng,… Trong thời gian tới Tôi sẽ tiếp tục xây dựng nhiều hơn nữa các bài tập (test và
solution) ứng dụng của cây tiền tố.
Trên đây là bài viết của Tôi về cây tiền tố, rất mong sự đóng góp ý kiến của quỹ Thầy Cô để
chuyên đề được tốt hơn.
Tôi xin chân thành cảm ơn!