-1.参考资料
OI wiki
0.定义
本文中,字符串下标均从1开始,所以可能和 OI wiki 有一些出入。
定义π[i] (前缀函数)表示 s[1…i] 的最长公共前缀后缀的长度。
形式化的,有:
π[i]=maxk=0…i{k|s[1…k]=s[i−(k−1)…i]}
特别的,π[1]=0。
1.预处理
首先有一个显然的结论:
π[i+1]≤π[i]+1
原因是 s[1…π[i+1]]=s[i−(π[i+1]−1)…i] 可以推出 s[1…π[i+1]−1]=s[i−(π[i+1]−1)…i−1]
令 k=π[i+1]−1 即得 s[1…k]=s[i−1−(k−1)…i−1]
根据 π[i] 的定义即得 π[i]≥k=pi[i+1]−1,得证。
于是考虑暴力枚举预处理,然后利用字符串哈希来判别字符串是否相等。
复杂度 O(∑n−1i=1π[i]+1−π[i]−1)=O(n)。
但是这个算法不一定是正确的,可能会被卡模数,我们考虑继续优化。
首先根据上面那个结论我们可以得到一个推论,就是 s[1…π[i+1]−1]=s[i−(π[i+1]−1)…i−1] ,因此我们考虑枚举 s[1…i−1] 的公共前缀后缀。
考虑怎么 O(1) 找到一个公共前缀后缀的前驱(i.e.仅次于这个公共前缀后缀的第二长度)。
设已知 s[1…j]=s[i−j+1…i],要求出 max{j′|s[1…j′]=s[i−j′+1…i],j′<j} 。
有
s[1…j′]=s[i−j′+1…i]=s[j−j′…j]
我们发现了
s[1…j′]=s[j−j′…j]
所以
j′=π[j]
Q.E.D.
转移的次数仍然是 O(n) 的,但是因为我们避免了字符串比较,所以总复杂度 O(n) 。
代码:
void init(char*s,int n,int*nxt){
nxt[1]=0;
for(int i=2,j;i<=n;i++){
while(j&&s[j+1]!=s[i])j=nxt[j];
if(s[i]==s[j+1])j++;
nxt[i]=j;
}
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· .NET Core内存结构体系(Windows环境)底层原理浅谈
· C# 深度学习:对抗生成网络(GAN)训练头像生成模型
· .NET 适配 HarmonyOS 进展
· 手把手教你更优雅的享受 DeepSeek
· 腾讯元宝接入 DeepSeek R1 模型,支持深度思考 + 联网搜索,好用不卡机!
· AI工具推荐:领先的开源 AI 代码助手——Continue
· 探秘Transformer系列之(2)---总体架构
· V-Control:一个基于 .NET MAUI 的开箱即用的UI组件库