1. CF 126B 在 KMP 失败函数与哈希的混合工程。
请说明 Codeforces 126B(Password)这道题如何将 KMP 失败函数与字符串哈希结合,用混合工程方法解决"最长子串同时是前缀、后缀并在中间出现"的问题?
- KMP 失败函数(前缀函数)的性质与滚动哈希的互补
- 如何用哈希判断前缀、后缀与中间子串是否相等
- 混合两种技术解决同一问题的工程取舍
CF 126B 要求找一个最长的子串,它同时是原串的前缀和后缀,并且还在字符串的中间位置出现。一种经典做法是先用 KMP 的失败函数求出所有既是前缀又是后缀的长度(即沿着 fail 链回溯),再对每个候选长度用哈希判断该前缀是否在中间出现过。哈希在这里的价值在于 O(1) 判断任意两段子串相等,因此可以预处理前缀哈希,然后对每个候选长度 l,检查所有中间位置起点 pos(1 <= pos && pos+l <= n-1)的子串哈希是否等于前缀哈希。KMP 负责给出"前缀=后缀"的候选集合,哈希负责"中间出现"的判定,两者结合把复杂度降到 O(n)。
单纯用 KMP 也能判断中间出现,但需要额外维护 next 数组并做"跳过"处理;哈希的好处是把"子串相等"变成 O(1) 的哈希值比较,思路更直观、代码更短。混合工程的核心是让 KMP 处理结构(前缀后缀关系),让哈希处理数值比较(任意段相等),各取所长。
public class CF126B {
static long[] h, p;
static final int BASE = 911382323;
static final long MOD = 1000000007L;
static long get(int l, int r) { // 闭区间 [l,r]
return ((h[r+1] - h[l]*p[r-l+1] % MOD) + MOD) % MOD;
}
public static void main(String[] args) {
String s = "eeee";
int n = s.length();
h = new long[n+1]; p = new long[n+1]; p[0]=1;
for (int i=0;i<n;i++){ h[i+1]=(h[i]*BASE+s.charAt(i))%MOD; p[i+1]=p[i]*BASE%MOD; }
// next 数组
int[] next = new int[n];
for (int i=0;i<n;i++) next[i]=0;
for (int i=1;i<n;i++){ int j=next[i-1]; while(j>0&&s.charAt(i)!=s.charAt(j)) j=next[j-1]; if(s.charAt(i)==s.charAt(j)) j++; next[i]=j; }
int ans=0;
// 遍历所有同时是前缀和后缀的长度
for (int len=next[n-1]; len>0; len=next[len-1]) {
long target = get(0,len-1);
boolean ok=false;
for (int st=1; st+len<=n-1; st++){ // 中间出现
if (get(st,st+len-1)==target){ ok=true; break; }
}
if (ok){ ans=len; break; }
}
System.out.println(ans>0 ? s.substring(0,ans) : "Just a legend");
}
}