Chương 2·Bài 21

KMP

Tìm mẫu PP trong văn bản TT bằng cách thử từng vị trí bắt đầu rồi so từng ký tự tốn tới ∣T∣⋅∣P∣|T| \cdot |P| phép so sánh; với aaaa…ab tìm trong aaaa…a dài 10610^6, mỗi vị trí gần như so hết mẫu rồi mới hỏng. Phí phạm nằm ở chỗ mỗi lần hỏng ta vứt hết phần đã khớp, trong khi phần ấy đã cho biết khá nhiều về những vị trí kế tiếp. Thuật toán Knuth-Morris-Pratt (KMP) giữ lại đúng phần còn dùng được, nhờ một mảng tính sẵn trên mẫu là hàm tiền tố. Con trỏ trên văn bản không bao giờ lùi, nên cả bài chạy trong O(∣T∣+∣P∣)O(|T| + |P|).

Hàm tiền tố còn là công cụ riêng: nó liệt kê mọi viền của xâu, cho chu kỳ ngắn nhất của mọi tiền tố, và biến thành một tự động dùng được trong quy hoạch động đếm xâu chứa mẫu.

Viền và hàm tiền tố

Một viền (border) của xâu ss là một xâu khác rỗng, ngắn hơn ss, vừa là tiền tố vừa là hậu tố của ss. Xâu aabaabaa có ba viền: aabaa, aa và a. Đánh số từ 1 và gọi π(i)\pi(i) là độ dài viền dài nhất của tiền tố s[1..i]s[1..i], bằng 0 nếu không có viền. Với ababa, π=(0,0,1,2,3)\pi = (0, 0, 1, 2, 3): tiền tố dài 5 có viền dài nhất aba.

Ô cam là vị trí đang tính, ô xanh là viền đang thử nối dài; bước cuối tô viền dài nhất của cả xâu.

Tính chất then chốt: viền của một viền cũng là viền. Nếu uu là viền của ss và vv là viền của uu thì vv là tiền tố của uu, nên là tiền tố của ss; và là hậu tố của uu, nên là hậu tố của ss. Ngược lại, mọi viền ngắn hơn của ss cũng là viền của viền dài nhất. Vậy các viền của s[1..i]s[1..i] chính là dãy π(i),π(π(i)),π(π(π(i))),…\pi(i), \pi(\pi(i)), \pi(\pi(\pi(i))), \dots cho tới 0; với aabaabaa là 5→2→1→05 \to 2 \to 1 \to 0.

Tính π\pi từ trái sang phải. Giả sử đã có π(1..i−1)\pi(1..i-1) và đặt j=π(i−1)j = \pi(i - 1). Viền của s[1..i]s[1..i] là một viền của s[1..i−1]s[1..i-1] nối thêm sis_i. Thử viền dài nhất trước: nếu sj+1=sis_{j+1} = s_i thì π(i)=j+1\pi(i) = j + 1. Nếu không, thử viền dài nhất kế tiếp của s[1..i−1]s[1..i-1], chính là π(j)\pi(j), và cứ thế; về tới j=0j = 0 mà vẫn không khớp thì π(i)=0\pi(i) = 0.

C++
for (ll i = 2, j = 0; i <= n; i++) {
    while (j && s[i] != s[j + 1]) j = pi[j];
    if (s[i] == s[j + 1]) j++;
    pi[i] = j;
}

Dù có vòng while, tổng thời gian là O(n)O(n). Biến jj tăng nhiều nhất 1 ở mỗi ii, mỗi lần lùi làm jj giảm ít nhất 1, và jj không bao giờ âm. Tổng số lần giảm không thể vượt tổng số lần tăng, tức không quá nn.

Hỏi nhanh

Trong xâu ababa, giá trị π(5)=3\pi(5) = 3 nói lên điều gì?

Ví dụCác Viền Của Xâu

Đề. Cho xâu SS dài tới 10610^6. In độ dài mọi viền theo thứ tự tăng dần, không có viền thì in 0.

Lời giải. Tính π\pi của cả xâu, rồi đi theo dãy π(n),π(π(n)),…\pi(n), \pi(\pi(n)), \dots tới 0: đó đúng là mọi viền, từ dài tới ngắn. Lưu lại rồi in ngược. Thử mọi độ dài và so hai đầu bằng tay là O(n2)O(n^2). Với aaaaa, π(5)=4\pi(5) = 4 và dãy là 4,3,2,14, 3, 2, 1.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
#define ll long long

const ll N = 1e6 + 5;
ll n, pi[N];
string s;

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> s;
    n = s.size();
    s = " " + s;
    for (ll i = 2, j = 0; i <= n; i++) {
        while (j && s[i] != s[j + 1]) j = pi[j];
        if (s[i] == s[j + 1]) j++;
        pi[i] = j;
    }
    vector<ll> v;
    for (ll k = pi[n]; k; k = pi[k]) v.push_back(k);
    reverse(v.begin(), v.end());
    if (v.empty()) cout << 0;
    for (ll x : v) cout << x << ' ';
    cout << '\n';
}
aaaaa

Tìm mẫu mà không quay lại văn bản

Quét văn bản TT từ trái sang, giữ jj là số ký tự đầu của PP khớp với hậu tố dài nhất của phần văn bản đã đọc. Đọc thêm TiT_i: nếu Pj+1=TiP_{j+1} = T_i thì jj tăng; nếu không, phần khớp mới phải là một viền của P[1..j]P[1..j] nối thêm TiT_i, nên lùi j←π(j)j \gets \pi(j) và thử lại, đúng như lúc tính π\pi. Khi j=∣P∣j = |P| là một lần xuất hiện kết thúc tại ii; ghi nhận rồi đặt j←π(∣P∣)j \gets \pi(|P|) để tiếp tục, chứ không về 0, nhờ vậy các lần xuất hiện chồng lấn không bị bỏ sót.

Hàng P là vị trí của mẫu ứng với j hiện tại; con trỏ trên văn bản không bao giờ lùi.

Con trỏ ii chạy đúng một lượt qua văn bản, còn jj tăng nhiều nhất 1 mỗi bước và mỗi lần lùi giảm ít nhất 1; tổng thời gian O(∣T∣+∣P∣)O(|T| + |P|) kể cả tính π\pi, bộ nhớ O(∣P∣)O(|P|). Một mẹo cài gọn khác: tính π\pi của xâu ghép P+#+TP + \# + T với # không xuất hiện trong hai xâu; mỗi vị trí trong phần TT có π=∣P∣\pi = |P| là một lần xuất hiện. KMP chỉ cần so hai phần tử bằng nhau, nên chạy nguyên vẹn trên dãy số, dãy cặp hay bất cứ thứ gì so sánh được.

Hỏi nhanh

Khi jj ký tự đầu của mẫu đã khớp nhưng ký tự kế tiếp không khớp, vì sao có thể chuyển jj thành π(j)\pi(j)?

Ví dụĐếm Lần Khớp

Đề. Cho văn bản TT và mẫu PP, mỗi xâu dài tới 10610^6. Đếm số lần PP xuất hiện trong TT, kể cả chồng lấn.

Lời giải. Tính π\pi trên mẫu, quét văn bản như trên, mỗi lần jj chạm ∣P∣|P| thì đếm và lùi về π(∣P∣)\pi(|P|). Đặt jj về 0 sau mỗi lần khớp là lỗi kinh điển: với aaa trong aaaaa nó đếm 1 thay vì 3. Hai xâu đánh số từ 1 bằng cách thêm một ký tự đệm ở đầu. Với ví dụ, mỗi khối năm chữ a có ba vị trí khớp, tổng 6.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
#define ll long long

const ll N = 1e6 + 5;
ll pi[N];
string t, p;

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> t >> p;
    ll n = t.size(), m = p.size();
    t = " " + t;
    p = " " + p;
    for (ll i = 2, j = 0; i <= m; i++) {
        while (j && p[i] != p[j + 1]) j = pi[j];
        if (p[i] == p[j + 1]) j++;
        pi[i] = j;
    }
    ll ans = 0;
    for (ll i = 1, j = 0; i <= n; i++) {
        while (j && t[i] != p[j + 1]) j = pi[j];
        if (t[i] == p[j + 1]) j++;
        if (j == m) {
            ans++;
            j = pi[j];
        }
    }
    cout << ans << '\n';
}
aaaaabaaaaa
aaa

Ví dụXóa Mẫu Khi Vừa Xuất Hiện

Đề. Đọc lần lượt các ký tự của ss (∣s∣≤106|s| \le 10^6), nối vào cuối một xâu tạm; ngay khi mẫu pp (∣p∣≤36|p| \le 36) thành hậu tố của xâu tạm thì xóa đoạn đó. In xâu cuối cùng, rỗng thì in FRULA.

Lời giải. Xóa thật rồi quét lại từ đầu là O(∣s∣2)O(|s|^2). Giữ xâu tạm như một ngăn xếp ký tự, kèm theo mỗi ký tự là trạng thái KMP jj ngay sau khi đặt nó. Thêm ký tự mới: lấy trạng thái của ký tự đang ở đỉnh (0 nếu ngăn xếp rỗng), chạy một bước KMP. Nếu trạng thái thành ∣p∣|p| thì bật ra ∣p∣|p| ký tự; trạng thái của ký tự mới ở đỉnh chính là trạng thái đúng của phần còn lại, nên không cần tính lại gì. Mỗi ký tự được đẩy và bật nhiều nhất một lần, mỗi bước KMP tốn trung bình O(1)O(1). Với ví dụ, zab bị xóa hai lần, còn lại xyab.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
#define ll long long

const ll N = 1e6 + 5;
ll pi[40], st[N], top;
char ch[N];
string s, p;

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> s >> p;
    ll m = p.size();
    p = " " + p;
    for (ll i = 2, j = 0; i <= m; i++) {
        while (j && p[i] != p[j + 1]) j = pi[j];
        if (p[i] == p[j + 1]) j++;
        pi[i] = j;
    }
    for (char c : s) {
        ll j = top ? st[top] : 0;
        while (j && c != p[j + 1]) j = pi[j];
        if (c == p[j + 1]) j++;
        ch[++top] = c;
        st[top] = j;
        if (j == m) top -= m;
    }
    if (!top) cout << "FRULA";
    for (ll i = 1; i <= top; i++) cout << ch[i];
    cout << '\n';
}
xyabzabzab
zab

Chu kỳ, lặp và cây viền

Số kk là một chu kỳ của xâu ss dài nn nếu si=si+ks_i = s_{i+k} với mọi ii hợp lệ, tức dịch ss sang phải kk ô thì phần chồng lên nhau trùng khớp. Xâu có chu kỳ kk khi và chỉ khi nó có viền dài n−kn - k: phần chồng lên nhau chính là tiền tố và hậu tố dài n−kn - k. Vì vậy chu kỳ ngắn nhất là n−π(n)n - \pi(n), ứng với viền dài nhất.

Kéo k để dịch xâu; k là chu kỳ khi mọi ô chồng nhau đều xanh.

Chu kỳ chưa chắc chia hết độ dài: abcabca có chu kỳ 3 dù dài 7. Xâu là tt lặp nguyên vẹn c≥2c \ge 2 lần khi và chỉ khi chu kỳ ngắn nhất kk chia hết nn và k<nk < n; khi đó số lần lặp lớn nhất là n/kn / k. Điều này đúng nhờ một bổ đề quen: nếu kk là chu kỳ nhỏ nhất và một chu kỳ dd chia hết nn thì kk cũng chia hết dd, nên không có cách lặp nào “thô” hơn bị bỏ sót.

Cây viền. Nối mỗi độ dài k≥1k \ge 1 với cha π(k)\pi(k) ta được một cây gốc 0: mỗi số có đúng một cha nhỏ hơn nó, cứ đi theo cha mãi sẽ về 0. Các số gặp trên đường từ kk về 0 gọi là tổ tiên của kk, còn các số mà đi theo cha sẽ qua kk (kể cả kk) tạo thành cây con của kk; chương 3 sẽ học kỹ về cây. Các viền của tiền tố dài kk là các tổ tiên của kk. Ngược lại, tiền tố dài kk xuất hiện kết thúc tại vị trí ii khi và chỉ khi kk là một viền của s[1..i]s[1..i] (hoặc chính s[1..i]s[1..i]), tức kk là tổ tiên của ii. Vậy số lần tiền tố dài kk xuất hiện trong ss bằng số nút trong cây con của kk, tính được bằng cách cộng dồn cnt[π(i)]+=cnt[i]\mathrm{cnt}[\pi(i)] \mathrel{+}= \mathrm{cnt}[i] với ii giảm dần, xuất phát từ cnt[i]=1\mathrm{cnt}[i] = 1.

Cha của k là π(k); bấm một nút để đếm số lần tiền tố tương ứng xuất hiện.

Ví dụChu Kỳ Của Các Tiền Tố

Đề. Nhiều test (tổng độ dài 10610^6). Với mỗi xâu SS, liệt kê theo độ dài tăng dần mọi tiền tố là một xâu ngắn hơn lặp nguyên vẹn ít nhất 2 lần, kèm số lần lặp lớn nhất. Mỗi test in dòng Test case #x rồi một dòng trống ở cuối.

Lời giải. Với tiền tố dài ii, chu kỳ ngắn nhất là k=i−π(i)k = i - \pi(i). Tiền tố là lặp khi π(i)>0\pi(i) > 0 và kk chia hết ii; số lần lặp là i/ki / k. Một lượt tính π\pi trả lời mọi tiền tố, O(n)O(n). Với ababab, π(4)=2\pi(4) = 2 cho 4=2⋅24 = 2 \cdot 2 và π(6)=4\pi(6) = 4 cho 6=3⋅26 = 3 \cdot 2; π(5)=3\pi(5) = 3 có chu kỳ 2 không chia hết 5.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
#define ll long long

const ll N = 1e6 + 5;
ll pi[N];

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    ll T;
    cin >> T;
    for (ll tc = 1; tc <= T; tc++) {
        ll n;
        string s;
        cin >> n >> s;
        s = " " + s;
        for (ll i = 2, j = 0; i <= n; i++) {
            while (j && s[i] != s[j + 1]) j = pi[j];
            if (s[i] == s[j + 1]) j++;
            pi[i] = j;
        }
        cout << "Test case #" << tc << '\n';
        for (ll i = 2; i <= n; i++) {
            ll k = i - pi[i];
            if (pi[i] && i % k == 0) cout << i << ' ' << i / k << '\n';
        }
        cout << '\n';
    }
}
1
6
ababab

Ví dụThông Điệp Ngắn Nhất

Đề. Một thông điệp được viết lặp đi lặp lại vô hạn; ta thấy đúng L≤106L \le 10^6 ký tự đầu, xâu SS. In độ dài nhỏ nhất có thể của thông điệp.

Lời giải. Thông điệp dài kk thì SS có chu kỳ kk; ngược lại SS có chu kỳ kk thì thông điệp S[1..k]S[1..k] lặp vô hạn sinh ra đúng SS. Ở đây không đòi kk chia hết LL, vì đoạn quan sát có thể dừng giữa một lần lặp. Đáp án là chu kỳ ngắn nhất L−π(L)L - \pi(L). Với abcabca, π(7)=4\pi(7) = 4 (viền abca), đáp án 3.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
#define ll long long

const ll N = 1e6 + 5;
ll n, pi[N];
string s;

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n >> s;
    s = " " + s;
    for (ll i = 2, j = 0; i <= n; i++) {
        while (j && s[i] != s[j + 1]) j = pi[j];
        if (s[i] == s[j + 1]) j++;
        pi[i] = j;
    }
    cout << n - pi[n] << '\n';
}
7
abcabca

KMP trên dãy số và xâu vòng

Dãy sai khác một hằng số. Muốn biết đoạn a[l..l+m−1]a[l..l+m-1] có bằng bb cộng một hằng số không, so hai dãy hiệu kề nhau: cộng cùng một số không đổi hiệu, và hai dãy có cùng dãy hiệu thì sai khác một hằng số. Bài thành tìm dãy hiệu của bb trong dãy hiệu của aa bằng KMP trên số nguyên, nhớ trường hợp m=1m = 1 có dãy hiệu rỗng và khớp mọi vị trí.

Xâu vòng. Các phép xoay của AA là các đoạn dài ∣A∣|A| của A+AA + A. Hỏi BB có phải một phép xoay của AA không, hay có bao nhiêu điểm bắt đầu trên vòng cho ra BB, là tìm BB trong A+AA + A (bỏ ký tự cuối để không đếm trùng điểm đầu). Với các tập điểm trên vòng tròn, như vạch trên mặt đồng hồ, ta sắp các điểm rồi lấy khoảng cách giữa hai điểm liên tiếp theo vòng: quay cả hình không đổi các khoảng cách, chỉ xoay vòng dãy của chúng.

Ví dụHai Mặt Số Không Mốc

Đề. Hai hình, mỗi hình có n≤2⋅105n \le 2 \cdot 10^5 vạch ở các góc nguyên khác nhau trong [0,360000)[0, 360000). Hỏi có quay được hình thứ nhất (theo modulo 360000) để hai tập vạch trùng nhau không; in possible hoặc impossible.

Lời giải. Sắp góc của mỗi hình, lấy dãy khoảng cách theo vòng di=ai+1−aid_i = a_{i+1} - a_i và dn=a1+360000−and_n = a_1 + 360000 - a_n. Quay hình là cộng cùng một góc vào mọi vạch, nên tập vạch của hình thứ nhất sau khi quay trùng hình thứ hai khi và chỉ khi dãy khoảng cách của hình hai là một phép xoay vòng của dãy khoảng cách hình một. Tìm dãy của hình hai trong dãy hình một viết hai lần bằng KMP trên số nguyên, O(n)O(n) sau khi sắp. Với ví dụ, cả hai dãy đều là (90000,90000,90000,90000)(90000, 90000, 90000, 90000).

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
#define ll long long

const ll N = 2e5 + 5, M = 360000;
ll n, a[N], b[N], t[2 * N], p[N], pi[N];

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n;
    for (ll i = 1; i <= n; i++) cin >> a[i];
    for (ll i = 1; i <= n; i++) cin >> b[i];
    sort(a + 1, a + n + 1);
    sort(b + 1, b + n + 1);
    for (ll i = 1; i <= n; i++) {
        t[i] = t[i + n] = (i < n ? a[i + 1] - a[i] : a[1] + M - a[n]);
        p[i] = (i < n ? b[i + 1] - b[i] : b[1] + M - b[n]);
    }
    for (ll i = 2, j = 0; i <= n; i++) {
        while (j && p[i] != p[j + 1]) j = pi[j];
        if (p[i] == p[j + 1]) j++;
        pi[i] = j;
    }
    bool ok = 0;
    for (ll i = 1, j = 0; i < 2 * n && !ok; i++) {
        while (j && t[i] != p[j + 1]) j = pi[j];
        if (t[i] == p[j + 1]) j++;
        if (j == n) ok = 1;
    }
    cout << (ok ? "possible" : "impossible") << '\n';
}
4
0 90000 180000 270000
45000 135000 225000 315000

Tự động KMP trong quy hoạch động

Trạng thái jj của KMP, “đang khớp jj ký tự đầu của mẫu”, gói gọn mọi thứ cần biết về phần văn bản đã đọc để quyết định mẫu có xuất hiện tiếp hay không. Tính sẵn bảng chuyển go[j][c]\mathrm{go}[j][c]: từ trạng thái j<∣P∣j < |P| đọc chữ cc thì sang trạng thái nào. Bảng tính được trong O(∣P∣⋅Σ)O(|P| \cdot \Sigma), với Σ\Sigma là số chữ cái, bằng chính quy tắc lùi: go[j][c]=j+1\mathrm{go}[j][c] = j + 1 nếu Pj+1=cP_{j+1} = c, ngược lại bằng go[π(j)][c]\mathrm{go}[\pi(j)][c] (với go[0][c]=0\mathrm{go}[0][c] = 0 khi c≠P1c \ne P_1). Đây là một tự động (automaton) hữu hạn có ∣P∣+1|P| + 1 trạng thái.

Tự động này làm trạng thái cho quy hoạch động trên xâu: đếm xâu độ dài nn chứa (hoặc không chứa) mẫu, xâu con chung dài nhất tránh một mẫu cấm, điền ký tự để mẫu xuất hiện nhiều nhất. dp[i][j]\mathrm{dp}[i][j] là số cách viết ii ký tự đầu mà tự động đang ở trạng thái jj; thêm một chữ là đi theo go\mathrm{go}. Đây cũng là khuôn của quy hoạch động chữ số (bài 13) với trạng thái là một tự động, và mở rộng tự nhiên sang nhiều mẫu với Aho-Corasick (bài 73).

Bảng go: ô xanh lá là chuyển tiến một ký tự, ô cam là chuyển vừa dùng khi chạy xâu thử.

Ví dụĐếm Xâu Có Mẫu Bắt Buộc

Đề. Cho N≤1000N \le 1000 và mẫu chữ hoa PP (∣P∣≤100|P| \le 100). Đếm số xâu độ dài NN trên 26 chữ hoa có chứa PP ít nhất một lần, theo modulo 109+710^9 + 7.

Lời giải. Đếm bù: lấy 26N26^N trừ số xâu không chứa PP. Đọc một xâu từng chữ qua tự động KMP của PP; xâu không chứa PP khi và chỉ khi trạng thái chưa bao giờ chạm ∣P∣|P|. Vậy chỉ cần đếm số xâu theo trạng thái hiện tại. Với N=3N = 3 và AB: có 263=1757626^3 = 17576 xâu, trong đó 52 xâu chứa AB (26 xâu dạng ABx và 26 xâu dạng xAB, không xâu nào thuộc cả hai).

Bước 1: hàm tiền tố của mẫu. Như mọi bài KMP, mẫu đánh số từ 1 nhờ ký tự đệm ở đầu.

C++
for (ll i = 2, j = 0; i <= m; i++) {
    while (j && p[i] != p[j + 1]) j = pi[j];
    if (p[i] == p[j + 1]) j++;
    pi[i] = j;
}

Bước 2: bảng chuyển của tự động. go[j][c] là trạng thái mới khi đang ở jj (đã khớp jj ký tự đầu của mẫu) và đọc chữ cc. Khớp ký tự kế thì lên j+1j + 1; không khớp thì đi như từ trạng thái π(j)\pi(j), đã tính xong vì π(j)<j\pi(j) < j. Bảng biến mỗi bước của KMP thành một lần tra, không còn vòng while.

C++
for (ll j = 0; j < m; j++)
    for (ll c = 0; c < 26; c++) {
        if (p[j + 1] - 'A' == c) go[j][c] = j + 1;
        else go[j][c] = j ? go[pi[j]][c] : 0;
    }

Bước 3: đếm theo trạng thái. dp[j]\mathrm{dp}[j] là số xâu đã dựng có trạng thái j<∣P∣j < |P|. Thêm một chữ: mọi xâu ở jj đi sang go[j][c] với 26 chữ cc; chuyển nào chạm ∣P∣|P| thì bỏ, vì xâu đó đã chứa mẫu. Song song tính 26N26^N.

C++
for (ll i = 0; i < n; i++) {
    all = all * 26 % MOD;
    for (ll j = 0; j < m; j++) nd[j] = 0;
    for (ll j = 0; j < m; j++)
        for (ll c = 0; c < 26; c++)
            if (go[j][c] < m) nd[go[j][c]] = (nd[go[j][c]] + dp[j]) % MOD;
    for (ll j = 0; j < m; j++) dp[j] = nd[j];
}

Đáp án là 26N26^N trừ tổng các dp[j]\mathrm{dp}[j], cộng thêm MOD để không âm.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
#define ll long long

const ll MOD = 1e9 + 7;
ll n, m, pi[105], go[105][26], dp[105], nd[105];
string p;

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n >> p;
    m = p.size();
    p = " " + p;
    for (ll i = 2, j = 0; i <= m; i++) {
        while (j && p[i] != p[j + 1]) j = pi[j];
        if (p[i] == p[j + 1]) j++;
        pi[i] = j;
    }
    for (ll j = 0; j < m; j++)
        for (ll c = 0; c < 26; c++) {
            if (p[j + 1] - 'A' == c) go[j][c] = j + 1;
            else go[j][c] = j ? go[pi[j]][c] : 0;
        }
    dp[0] = 1;
    ll all = 1;
    for (ll i = 0; i < n; i++) {
        all = all * 26 % MOD;
        for (ll j = 0; j < m; j++) nd[j] = 0;
        for (ll j = 0; j < m; j++)
            for (ll c = 0; c < 26; c++)
                if (go[j][c] < m) nd[go[j][c]] = (nd[go[j][c]] + dp[j]) % MOD;
        for (ll j = 0; j < m; j++) dp[j] = nd[j];
    }
    ll bad = 0;
    for (ll j = 0; j < m; j++) bad = (bad + dp[j]) % MOD;
    cout << (all - bad + MOD) % MOD << '\n';
}
3
AB

Chi phí N⋅∣P∣⋅26≈2,6⋅106N \cdot |P| \cdot 26 \approx 2{,}6 \cdot 10^6.

Khi đi thi

  • Thấy tìm mẫu, viền, chu kỳ hay “lặp nguyên vẹn” trên xâu dài tới 10610^6: tính π\pi; KMP không có xác suất sai như hash (bài 19) và không tốn bộ nhớ như cây hậu tố.
  • Thống nhất một quy ước chỉ số (ở đây đánh số từ 1, π\pi theo độ dài) và giữ nó trong cả bài; sau mỗi lần khớp lùi về π(∣P∣)\pi(|P|), không về 0, để giữ các lần chồng lấn.
  • Bài so sánh có biến đổi (cộng hằng số, xoay vòng, đổi bảng chữ cái) thường quy về KMP trên dãy hiệu hoặc trên A+AA + A; bài đếm xâu chứa hay tránh mẫu thì dùng tự động KMP làm trạng thái quy hoạch động.

Tóm tắt

  • π(i)\pi(i) là độ dài viền dài nhất của s[1..i]s[1..i]; mọi viền là dãy π(i),π(π(i)),…\pi(i), \pi(\pi(i)), \dots; tính π\pi trong O(n)O(n) vì jj tăng tối đa nn lần.
  • KMP giữ jj là độ dài khớp hiện tại, lùi theo π\pi khi hỏng, không quay lại văn bản; tổng O(∣T∣+∣P∣)O(|T| + |P|).
  • Chu kỳ ngắn nhất là n−π(n)n - \pi(n); xâu là lặp nguyên vẹn khi chu kỳ ấy chia hết nn và nhỏ hơn nn.
  • Cây viền (cha của kk là π(k)\pi(k)) cho số lần xuất hiện của mọi tiền tố bằng cộng dồn cây con.
  • Bảng chuyển go[j][c]\mathrm{go}[j][c] biến KMP thành tự động, làm trạng thái cho quy hoạch động đếm xâu.

Bài tập

Đọc thêm