字符串哈希 - # 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
满足条件的字符串有 a、b、ab、ba。这些字符串的最大长度为 \(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\)
- 预处理哈希:\(O(n)\)
- 二分次数:\(O(\log n)\)
- 单次
check:枚举 \(O(n)\) 个子串,set/map操作 \(O(\log n)\)
总复杂度:\(O(n \log^2 n)\)
对于 \(n=5005\) 完全可以轻松通过。

浙公网安备 33010602011771号