Thuật toán Manacher¶
Dịch từ bài viết gốc của CP-Algo.
Nhận xét¶
Nhận thấy nếu xâu độ dài \(L\) đối xứng có tâm tại \(i\), thì xâu độ dài \(L-2,L-4,\cdots\) có tâm đặt tại \(i\) cũng sẽ đối xứng.
Định nghĩa¶
- Các kí tự được đánh số từ \(1\).
- \(d_{1i}\) là độ dài của xâu đối xứng dài nhất đặt tại \(i\) (không tính \(i\)). Ví dụ xâu \(s = abababc\) có \(d_{1}[4]=3\):
- \(d_{2i}\) là độ dài của xâu đối xứng dài nhất có tâm chẵn tại \(i\), nghĩa là xâu có tâm là 2 kí tự \(s_i\) và \(s_{i+1}\). Ví dụ xâu \(s = cbaabd\) có \(d_{2}[4]=2\):
- Với mỗi vị trí \(i\), gọi \(l, r\) là vị trí của xâu palindrome gần với vị trí \(i\) nhất (bên phải nhất) tính đến thời điểm hiện tại. Ban đầu \(l = 0, r = -1\).
Giải thuật¶
Tính mảng \(d_1\)¶
Với mỗi vị trí \(i\), ta xét các trường hợp sau:
-
Nếu \(i>r\), chạy thuật toán trâu:
-
Ngược lại, xét trường hợp \(i \leq r\), nghĩa là \(i\) thuộc vào đoạn palin gần nhất đang xét. Nhận thấy ở đoạn \([l,r]\), vị trí đối xứng với vị trí \(i\) qua tâm \(\frac{l+r}{2}\) là vị trí \(l+(r-i)\). Đặt \(j=l+(r-i)\). Ở đây ta tiếp tục có \(2\) trường hợp con:
- Đoạn \([j-d_{1j}+1,j+d_{1j}-1]\) nằm trong đoạn \([l,r]\). Vì \(s_j\) đối xứng với \(s_i\) mà đoạn to \([l,r]\) đối xứng qua tâm \(\Rightarrow\) đoạn \([j-d_{1j}+1,j+d_{1j}-1]\) và \([i-d_{1j}+1,i+d_{1j}-1]\) đối xứng. Như vậy ta gán \(d_{1i} = d_{1j}\).
\[ \ldots\ \overbrace{ s_{l+1}\ \ldots\ \underbrace{ s_{j-d_{odd}[j]+1}\ \ldots\ s_j\ \ldots\ s_{j+d_{odd}[j]-1}\ }_\text{palindrome}\ \ldots\ \underbrace{ s_{i-d_{odd}[j]+1}\ \ldots\ s_i\ \ldots\ s_{i+d_{odd}[j]-1}\ }_\text{palindrome}\ \ldots\ s_{r-1}\ }^\text{palindrome}\ \ldots \]- Đoạn \([j-d_{1j}+1,j+d_{1j}-1]\) chỉ giao 1 phần với đoạn \([l,r]\). Lúc này ta không thể kết luận được \(d_{1i} = d_{1j}\), bởi vì mình không quản lý cái phần nằm ngoài đoạn \([l,r]\), nên chưa đủ cơ sở để khẳng định \(d_{1i} = d_{1j}\). Khi đó ta gán \(d_{1i}=r-i\), và tiếp tục chạy thuật trâu để mở rộng tâm ra.
\[ \ldots\ \overbrace{ \underbrace{ s_{l+1}\ \ldots\ s_j\ \ldots\ s_{j+(j-l)-1}\ }_\text{palindrome}\ \ldots\ \underbrace{ s_{i-(r-i)+1}\ \ldots\ s_i\ \ldots\ s_{r-1} }_\text{palindrome}\ }^\text{palindrome}\ \underbrace{ \ldots \ldots \ldots \ldots \ldots }_\text{try moving here} \] -
Lưu ý, sau khi xét tất cả TH trên xong, ta cần cập nhật lại vị trí của biên \(l\) và \(r\)
Tính mảng \(d_2\)¶
Thêm các kí tự DUMMY
vào đầu, cuối, và giữa các kí tự xâu, nghĩa là các kí tự mà không bao giờ xuất hiện trong xâu. Ví dụ xâu orz
trở thành .o.r.z.
với DUMMY = .
.
Chạy thuật toán Manacher với tâm lẻ trên xâu mới, tạm gọi mảng này là mảng \(d[]\). Lúc này nhận thấy các xâu đối xứng tâm chẵn trên xâu gốc đã trở thành xâu đối xứng tâm lẻ với tâm đặt tại các kí tự DUMMY trên xâu mới. Như vậy ta có \(d_{2i}=\dfrac{d_{2 \times i + 1}}{2}\) (vì thêm các kí tự DUMMY làm số kí tự gấp đôi lên)
Độ phức tạp¶
Nhận xét mỗi lần lặp \(r\) sẽ được tăng lên ít nhất \(1\) đơn vị, và \(r\) không thể nào bị giảm đi, nên độ phức tạp của thuật toán sẽ là \(O(n)\).
Các bạn có thể tham khảo chi tiết chứng minh tại link ở đầu bài.