BOJ 1605

 

백준 1605, LCP

오랜만에 다시 BOJ에 들르게 되었다.
cpp 사용을 안 해서 많이 퇴화되어, 자체 구현은 어림도 없었다.
cp-algorithms.com 의 구현체를 썼다.

문제 해결 방법은 크게 두 가지가 있다.

LCP :
모든 suffix에 대해서 2회 이상 반복되는 prefix 의 최대 길이 ,
Longest(최대 길이) Common(2회 이상 반복) Prefix 의 정의와 일치한다.

Binary search + Rabin-Karp :
길이 L인 substring 들을 전체 길이 N인 문자열과 비교.
Hashing으로 string matching을 빠르게 하는 대표적인 알고리즘 Rabin-Karp를 사용한다.
이분 탐색으로 탐색 횟수가 log(N) 으로 맞춰진다.

suffix 이자 prefix가 되는 substring 을 찾는 Z-function 밖에 생각이 안 났었는데,
답을 보고서야 LCP 라는게 있었다는게 기억났다.

직접 string matching을 하는 방법도 생각은 했는데,
string matching을 충분히 빠르게 하는 방법이 없다고 생각을 했었다.
밑의 다른 풀이를 보고서야 이해를 할 수 있었다.

Source Code

1605링크

#define _CRT_SECURE_NO_WARNINGS
#include <bits/stdc++.h>

#define ll long long

#define pii pair<int, int>
#define pll pair<ll, ll>
#define MAXI 1000000000
#define cplx complex<double>
#define MOD 9901
const double PI = acos(-1);
const ll INF = INT_MAX / 4;

using namespace std;

// https://cp-algorithms.com/string/suffix-array.html
vector<int> sort_cyclic_shifts(string const &s) {
    int n = s.size();
    const int alphabet = 256;
    vector<int> p(n), c(n), cnt(max(alphabet, n), 0);
    for (int i = 0; i < n; i++)
        cnt[s[i]]++;
    for (int i = 1; i < alphabet; i++)
        cnt[i] += cnt[i - 1];
    for (int i = 0; i < n; i++)
        p[--cnt[s[i]]] = i;
    c[p[0]] = 0;
    int classes = 1;
    for (int i = 1; i < n; i++) {
        if (s[p[i]] != s[p[i - 1]])
            classes++;
        c[p[i]] = classes - 1;
    }
    vector<int> pn(n), cn(n);
    for (int h = 0; (1 << h) < n; ++h) {
        for (int i = 0; i < n; i++) {
            pn[i] = p[i] - (1 << h);
            if (pn[i] < 0)
                pn[i] += n;
        }
        fill(cnt.begin(), cnt.begin() + classes, 0);
        for (int i = 0; i < n; i++)
            cnt[c[pn[i]]]++;
        for (int i = 1; i < classes; i++)
            cnt[i] += cnt[i - 1];
        for (int i = n - 1; i >= 0; i--)
            p[--cnt[c[pn[i]]]] = pn[i];
        cn[p[0]] = 0;
        classes = 1;
        for (int i = 1; i < n; i++) {
            pair<int, int> cur = {c[p[i]], c[(p[i] + (1 << h)) % n]};
            pair<int, int> prev = {c[p[i - 1]], c[(p[i - 1] + (1 << h)) % n]};
            if (cur != prev)
                ++classes;
            cn[p[i]] = classes - 1;
        }
        c.swap(cn);
    }
    return p;
}

vector<int> suffix_array_construction(string s) {
    s += "$";
    vector<int> sorted_shifts = sort_cyclic_shifts(s);
    sorted_shifts.erase(sorted_shifts.begin());
    return sorted_shifts;
}

// https://cp-algorithms.com/string/suffix-array.html#longest-common-prefix-of-two-substrings-without-additional-memory
vector<int> lcp_construction(string const &s, vector<int> const &p) {
    int n = s.size();
    vector<int> rank(n, 0);
    for (int i = 0; i < n; i++)
        rank[p[i]] = i;

    int k = 0;
    vector<int> lcp(n - 1, 0);
    for (int i = 0; i < n; i++) {
        if (rank[i] == n - 1) {
            k = 0;
            continue;
        }
        int j = p[rank[i] + 1];
        while (i + k < n && j + k < n && s[i + k] == s[j + k])
            k++;
        lcp[rank[i]] = k;
        if (k)
            k--;
    }
    return lcp;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int input_len;
    string input_str;
    cin >> input_len;
    cin >> input_str;

    vector<int> SA = suffix_array_construction(input_str);
    vector<int> lcp_result = lcp_construction(input_str, SA);

    int ans = 0;
    for (int i = 0; i < lcp_result.size(); ++i) {
        ans = (ans > lcp_result[i]) ? ans : lcp_result[i];
        // printf("%d\n", lcp_result[i]);
    }

    printf("%d", ans);

    return 0;
}

// Another solution from @readiz https://www.acmicpc.net/source/65147757
// 발상을 알면 풀이는 쉽다. 이분탐색 + 라빈카프를 활용한다.
/* #include <bits/stdc++.h>
using namespace std;

#define _D(...) // printf(__VA_ARGS__)
typedef long long ll;
typedef unsigned long long ull;

char buf[200'001];

constexpr int HLEN = 10'000'007;
char cnt[4][HLEN];
int p[4] = {11, 37, 5381, 10007};
bool rk(int f, int len) {
    _D("rabinkarp: %d / %d\n", f, len);
    memset(cnt, 0, 4 * HLEN * sizeof(char));
    ll ival[4] = {
        0,
    };
    ll unit[4] = {1, 1, 1, 1};
    for (int k = 0; k < 4; ++k) {
        for (int i = 0; i < f; ++i) {
            ival[k] = ival[k] * p[k] + buf[i];
            ival[k] %= HLEN;

            unit[k] *= p[k];
            unit[k] %= HLEN;
        }
        cnt[k][ival[k]]++;
        _D("unit %d: %lld\n", k, unit[k]);
    }
    for (int i = f; i < len; ++i) {
        char cur = buf[i];
        char last = buf[i - f];
        int ansCnt = 0;
        for (int k = 0; k < 4; ++k) {
            ival[k] = ival[k] * p[k] + cur;
            ival[k] -= unit[k] * last;
            while (ival[k] < 0)
                ival[k] += HLEN;
            ival[k] %= HLEN;

            // _D("[%d] ival: %d\n", k, ival[k]);
            cnt[k][ival[k]]++;
            if (cnt[k][ival[k]] >= 2) ansCnt++;
        }
        _D("ansCnt: %d at %d\n", ansCnt, i);
        if (ansCnt >= 4) return true;
    }
    return false;
}

int main() {
    int N;
    scanf("%d", &N);
    scanf("%s", buf);

    int l = 1;
    int r = N;
    int ans = 0;
    for (int m = (l + r) >> 1; l <= r; m = (l + r) >> 1) {
        if (rk(m, N)) {
            l = m + 1; // 찾았으니 길이를 늘려본다.
            //printf("%d\n", l);
            ans = m;
        } else {
            r = m - 1; // 못찾았으니 길이를 줄인다.
        }
    }
    printf("%d\n", ans);

    return 0;
} */

wbjeon2k

Pursuite for Progress.

This work is licensed under a Attribution-NonCommercial 4.0 International license. Attribution-NonCommercial 4.0 International