← Все вопросы

Что такое массив LCP и как построить его за O(n) алгоритмом Касаи?

Задан 22 месяца назад1.4к просмотров2 ответа
9

Построил суффиксный массив, теперь нужен LCP — длины наибольших общих префиксов соседних суффиксов в sa. Наивно это O(n^2). Слышал про алгоритм Касаи за O(n), но не понимаю, почему он линейный, если внутри есть сравнение символов в цикле.

2 ответа

14
✓ Принятый ответ — помог автору

lcp[i] = длина наибольшего общего префикса суффиксов sa[i-1] и sa[i] (соседних в отсортированном порядке). Касаи использует ключевое наблюдение: если суффикс с позиции i имеет с соседом LCP = h, то суффикс с позиции i+1 имеет LCP с СВОИМ соседом не меньше h-1. Значит, переходя по суффиксам в порядке исходных позиций, можно только уменьшать h на 1 и доращивать — суммарно O(n) сравнений.

vector<int> build_lcp(const string& s, const vector<int>& sa) {
    int n = s.size();
    vector<int> rank(n), lcp(n, 0);
    for (int i = 0; i < n; ++i) rank[sa[i]] = i;
    int h = 0;
    for (int i = 0; i < n; ++i) {
        if (rank[i] > 0) {
            int j = sa[rank[i] - 1];
            while (i + h < n && j + h < n && s[i + h] == s[j + h]) ++h;
            lcp[rank[i]] = h;
            if (h > 0) --h;            // ключевая строка: переносим h-1 дальше
        } else h = 0;
    }
    return lcp;
}

Линейность: h суммарно увеличивается не более чем на n (каждый ++h сдвигает совпавший префикс), а уменьшается не больше n раз (по разу на итерацию). Итог — O(n) время, O(n) память. Именно строка --h обеспечивает амортизацию.

4

Зачем нужен LCP на практике: наибольшая повторяющаяся подстрока = max(lcp); число различных подстрок = n(n+1)/2 - Σ lcp[i]; сравнение/поиск подстрок через RMQ над LCP. То есть LCP — это «клей», который превращает суффиксный массив из просто сортировки в мощный инструмент. Без него SA умеет немного.

Ваш ответ

Войдите, чтобы ответить на вопрос.