KMP 알고리즘과 기타 문자열 처리 기법 활용

KMP 알고리즘의 nxt 배열을 살펴보겠습니다. 답은 n - nxt[n]으로 구할 수 있습니다.

코드

#include <iostream>
#include <vector>
#include <string>

void compute_next_array(int n, const std::string& s, std::vector<int>& nxt) {
    int j = 0;
    for (int i = 1; i < n; ++i) {
        while (j > 0 && s[i] != s[j]) {
            j = nxt[j - 1];
        }
        if (s[i] == s[j]) {
            j++;
        }
        nxt[i] = j;
    }
}

int main() {
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);
    int n;
    std::cin >> n;
    std::string a;
    std::cin >> a;

    std::vector<int> nxt(n);
    compute_next_array(n, a, nxt);

    std::cout << n - nxt[n - 1] << std::endl;

    return 0;
}

문자열 연결과 접두사 특징을 고려할 때 KMP 알고리즘을 다시 활용할 수 있습니다. 이 문제는 가장 긴 주기를 요구하므로, 남은 부분을 최소화해야 합니다. 따라서 nxt' 배열을 직접 구할 수 있습니다. 기본 원리는 동일합니다. j는 종종 nxt[i]와 같다는 것을 알 수 있습니다. 이를 통해 다음과 같은 점화식을 유도할 수 있습니다: nxt[i] = (nxt[nxt'[i]] == 0) ? fail_i : nxt[nxt'[i]] 어떤 경우에는 주기가 존재하지 않아 nxt'[i] < i / 2가 될 수 있습니다. 이 경우 ans를 업데이트하지 않으면 됩니다. 데이터 범위가 10^6이므로, 최대 ans10^12가 될 수 있어 long long 타입이 필요합니다.

코드

#include <iostream>
#include <vector>
#include <string>
#include <cmath>
#include <numeric>

void compute_kmp_arrays(int n, const std::string& s, std::vector<int>& fail, std::vector<int>& nxt) {
    int j = 0;
    for (int i = 1; i < n; ++i) {
        while (j > 0 && s[i] != s[j]) {
            j = fail[j - 1];
        }
        if (s[i] == s[j]) {
            j++;
        }
        fail[i] = j;
    }

    j = 0;
    for (int i = 1; i < n; ++i) {
        j = fail[i];
        nxt[i] = (j == 0) ? fail[i] : nxt[j - 1];
    }
}

int main() {
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);
    int n;
    std::cin >> n;
    std::string a;
    std::cin >> a;

    std::vector<int> fail(n);
    std::vector<int> nxt(n);
    compute_kmp_arrays(n, a, fail, nxt);

    long long total_period_sum = 0;
    for (int i = 0; i < n; ++i) {
        int current_period_len = (i + 1) - nxt[i];
        if (current_period_len >= std::ceil((i + 1) / 2.0) && nxt[i] > 0) {
            total_period_sum += current_period_len;
        }
    }

    std::cout << total_period_sum << std::endl;

    return 0;
}

문제: 최소한의 문자를 추가하여 문자열을 회문으로 만드세요. 전체 문자열의 끝에 닿는 가장 긴 접두사를 계산하면 됩니다. 즉, i + r[i] - 1 = n인 경우입니다.

코드

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <cmath>

int main() {
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);
    int n;
    std::cin >> n;
    std::string s;
    std::cin >> s;

    std::string reversed_s = s;
    std::reverse(reversed_s.begin(), reversed_s.end());
    std::string combined = s + "#" + reversed_s;

    int m = combined.length();
    std::vector<int> pi(m);
    for (int i = 1; i < m; i++) {
        int j = pi[i - 1];
        while (j > 0 && combined[i] != combined[j]) {
            j = pi[j - 1];
        }
        if (combined[i] == combined[j]) {
            j++;
        }
        pi[i] = j;
    }

    int longest_palindrome_suffix_prefix = pi[m - 1];
    int chars_to_add = n - longest_palindrome_suffix_prefix;

    std::cout << chars_to_add << std::endl;

    return 0;
}

DFS를 사용하여 트리의 모든 노드에서 루트까지의 XOR 합을 계산합니다. 노드 u에서 노드 v까지의 XOR 합은 루트까지의 XOR 합과 같습니다. 따라서 s[i] XOR s[j]를 최대화하는 것을 목표로 합니다. 각 s[i]에 대해 s[i] XOR s[j]를 최대화하는 s[j]를 찾습니다. 이는 트라이(Trie)에서 수행할 수 있습니다. 모든 s[i]를 바이너리 문자열로 트라이에 삽입합니다. 탐욕적 접근: 트라이에서 s[i]와 다른 비트를 가진 값이 존재하면 해당 경로를 따릅니다. 그렇지 않으면 같은 값을 가진 경로를 따릅니다. 상위 비트의 1은 하위 비트의 1의 합보다 항상 크므로 탐욕적 선택이 유효합니다. 모든 s[i]에 대해 최대 XOR 값을 찾습니다. 시간 복잡도는 O(n log α)이며, 여기서 α는 비트 수입니다. 세부 정보: 선행 0을 추가해야 합니다.

코드

#include <iostream>
#include <vector>
#include <algorithm>
#include <map>

const int MAX_BITS = 31; // Assuming 32-bit integers

struct TrieNode {
    TrieNode* children[2];
    TrieNode() {
        children[0] = children[1] = nullptr;
    }
};

void insert(TrieNode* root, int num) {
    TrieNode* curr = root;
    for (int i = MAX_BITS - 1; i >= 0; --i) {
        int bit = (num >> i) & 1;
        if (curr->children[bit] == nullptr) {
            curr->children[bit] = new TrieNode();
        }
        curr = curr->children[bit];
    }
}

int find_max_xor(TrieNode* root, int num) {
    TrieNode* curr = root;
    int max_xor_val = 0;
    for (int i = MAX_BITS - 1; i >= 0; --i) {
        int bit = (num >> i) & 1;
        int desired_bit = 1 - bit;
        if (curr->children[desired_bit] != nullptr) {
            max_xor_val |= (1 << i);
            curr = curr->children[desired_bit];
        } else {
            curr = curr->children[bit];
        }
    }
    return max_xor_val;
}

std::vector<int> adj[100005];
int xor_sums[100005];

void dfs(int u, int p, int current_xor) {
    xor_sums[u] = current_xor;
    for (auto& edge : adj[u]) {
        int v = edge.first;
        int weight = edge.second;
        if (v != p) {
            dfs(v, u, current_xor ^ weight);
        }
    }
}

int main() {
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);

    int n;
    std::cin >> n;

    for (int i = 0; i < n - 1; ++i) {
        int u, v, w;
        std::cin >> u >> v >> w;
        adj[u].push_back({v, w});
        adj[v].push_back({u, w});
    }

    dfs(1, 0, 0);

    TrieNode* root = new TrieNode();
    int max_overall_xor = 0;

    for (int i = 1; i <= n; ++i) {
        insert(root, xor_sums[i]);
        max_overall_xor = std::max(max_overall_xor, find_max_xor(root, xor_sums[i]));
    }

    std::cout << max_overall_xor << std::endl;

    return 0;
}

연결된 리스트를 만들고, 분할 정복 기법을 사용하여 접미사 배열을 계산하면 O(n log^2 n) 시간 복잡도로 해결할 수 있습니다. 접미사 배열 구현은 비교적 간단합니다.

태그: KMP 문자열 알고리즘 접두사 함수 회문 트라이

7월 21일 19:08에 게시됨