字符串哈希 - # AT_abc141_e [ABC141E] Who Says a Pun?

题目描述

给定一个长度为 \(N\) 的字符串 \(S\)
请你求出所有作为 \(S\) 的连续子串且在 \(S\) 中不重叠地出现至少两次的非空字符串中,最长的长度是多少。
更严格地说,求满足以下条件的正整数 \(len\) 的最大值:

  • \(l_1 + len \leq l_2\)
  • \(S[l_1 + i] = S[l_2 + i]\ (i = 0, 1, \ldots, len - 1)\)

存在整数 \(l_1, l_2\)\(1 \leq l_1, l_2 \leq N - len + 1\))使上述条件成立。若不存在这样的 \(len\),请输出 \(0\)

输入格式

输入以以下格式从标准输入读入。

\(N\) \(S\)

输出格式

输出作为 \(S\) 的连续子串且在 \(S\) 中不重叠地出现至少两次的非空字符串中,最长的长度。如果不存在这样的非空字符串,则输出 \(0\)

输入输出样例 #1

输入 #1

5
ababa

输出 #1

2

说明/提示

限制条件

  • \(2 \leq N \leq 5 \times 10^3\)
  • \(|S| = N\)
  • \(S\) 由小写英文字母组成

样例解释 1

满足条件的字符串有 ababba。这些字符串的最大长度为 \(2\),因此答案为 \(2\)。注意,虽然 aba 作为 \(S\) 的连续子串出现了两次,但无法取到满足 \(l_1 + len \leq l_2\)\(l_1\)\(l_2\)

二、解题思路

1. 核心策略:二分答案 + 字符串哈希

  • 满足二分答案性质:存在最优解的长度为 \(L\) ,必然存在长度为 \(1-L-1\) 的方案。
  • 答案 \(L\) 的合法范围:\(0 \le L \le \dfrac{n}{2}\)。 F(x) - 是否存在 2 个长度 x 的相同子串。
  • 长度 \(x\) 的不重叠的相同子串如何处理?
    map<LL, int> mp ,mp[x] 子串hash=x 第一次出现的位置。
    枚举不同的起始位置 \(i\) ,使用GetHash()函数长度为 \(x\) 的子串的hash值,\(i,x,mp.second\) 即可确定位置关系。

代码

#include <bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 5005;
const int BASE = 131;
const LL MOD1 = 1e9 + 7;
const LL MOD2 = 1e9 + 9;

int n;
char s[N];
LL pre1[N], pre2[N];  // 双前缀哈希数组
LL pow1[N], pow2[N];  // 幂次数组
set<LL> st;

// 预处理前缀哈希 + 幂数组
void init() {
    pow1[0] = pow2[0] = 1;
    for (int i = 1; i <= n; i++) {
        pow1[i] = pow1[i - 1] * BASE % MOD1;
        pow2[i] = pow2[i - 1] * BASE % MOD2;
        pre1[i] = (pre1[i - 1] * BASE + s[i]) % MOD1;
        pre2[i] = (pre2[i - 1] * BASE + s[i]) % MOD2;
    }
}

// 查询 [l,r] 子串合并哈希值
LL get_hash(int l, int r) {
    LL h1 = (pre1[r] - pre1[l - 1] * pow1[r - l + 1] % MOD1 + MOD1) % MOD1;
    LL h2 = (pre2[r] - pre2[l - 1] * pow2[r - l + 1] % MOD2 + MOD2) % MOD2;
    return h1 * MOD1 + h2;
}

// 检查:是否存在两个长度为 mid 的不重叠相同子串
bool check(int mid) {
    if (mid == 0) return true;
    map<LL, int> mp; // 哈希值 -> 该子串第一次出现的起始下标
    st.clear();

    // 枚举所有长度为 mid 的子串起点 i
    for (int i = 1; i + mid - 1 <= n; i++) {
        LL h = get_hash(i, i + mid - 1);
        if (st.count(h)) {
            // 不重叠条件:上一个起点 + 串长 <= 当前起点
            if (mp[h] + mid <= i)
                return true;
        } else {
            st.insert(h);
            mp[h] = i;
        }
    }
    return false;
}

int main() {
    // s+1 表示字符串从下标 1 开始存
    scanf("%d%s", &n, s + 1);
    init();

    // 二分答案:最大合法长度,上界 n/2
    int l = 0, r = n / 2, ans = 0;
    while (l < r) {
        // 上取整二分,避免死循环
        int mid = (l + r + 1) / 2;
        if (check(mid))
            l = mid;
        else
            r = mid - 1;
    }
    cout << l << '\n';
    return 0;
}

复杂度分析

设字符串长度 \(n \le 5005\)

  1. 预处理哈希:\(O(n)\)
  2. 二分次数:\(O(\log n)\)
  3. 单次 check:枚举 \(O(n)\) 个子串,set/map 操作 \(O(\log n)\)
    总复杂度:\(O(n \log^2 n)\)
    对于 \(n=5005\) 完全可以轻松通过。
posted @ 2026-06-11 15:16  alice_ss  阅读(5)  评论(0)    收藏  举报
//雪花飘落效果