专栏文章
题解:P5410 【模板】扩展 KMP/exKMP(Z 函数)
P5410题解参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @mioi8h2r
- 此快照首次捕获于
- 2025/12/02 19:38 3 个月前
- 此快照最后确认于
- 2025/12/02 19:38 3 个月前
太菜了,今天才刚学会。
题目链接。
Z 函数 定义
对于一个长度为 的字符串 ,定义函数 表示 和 (即以 开头的后缀)的最长公共前缀 (LCP) 的长度,则 被称为 的 Z 函数。 ——OI Wiki
Solution
Part 1 暴力碾标算
考虑暴力,很容易可以想道 的做法:
CPPz[1]=strlen(s+1);
for(int i=2;i<=strlen(s+1);i++){
for(int j=1,k=i;k<=strlen(s+1);k++,j++){
if(s[j]!=s[k]){
z[i]=j-1;
break;
}
z[i]=j;
}
}
只能拿到 分。
Part 2 正解
考虑跟 Manacher 一样的思路:
( 表示原字符串 , 表示以第 个字符为开头的 后缀, 表示与 的 LCP 最长的后缀)。

我们规定红色线段为 后缀中 的右端点目前最远的 。
那么 表示右端点, 表示左端点。
再解释一下。
设以红色线段为 的后缀为 ,以第 个节点为开头的后缀为 ,一个后缀 的 右端点为 左端点为 ,则:
那么我们这的绿色线段表示以 为左端点, 为右端点的 子串。
可以发现 会有一段相对应的绿色线段,由于与 相对应,所以左端点为 。
那么就肯定会有一个后缀 使得前缀为绿色线段,那么 的 Z 函数就可已从 来转移,因为 与 都是绿色前缀。
类似 Manacher 算法,我们分为两种情况:
-
小于等于 那么我们可以直接继承过来(即上图蓝色段)。
-
大于 那么我们只能取 到 的这段,因为外面段我们不能保证相同(即上图紫色段)。
继承后,我们再暴力一下,得到准确的答案。
最后还需要更新下 与 。
Code
CPP#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e7+10;
char a[N],b[N];
int ans;
int z[N];
int p[N];
signed main(){
ios::sync_with_stdio(false);
cin>>(a+1)>>(b+1);
int l=0,r=0;
z[1]=strlen(b+1);
for(int i=2;i<=strlen(b+1);i++){
if(i<r) z[i]=min(z[i-l+1],r-i+1);
while(i+z[i]<=strlen(b+1)&&b[i+z[i]]==b[z[i]+1]) {
z[i]++;
}
if(r<i+z[i]-1){
r=i+z[i]-1;
l=i;
}
}
for(int i=1;i<=strlen(b+1);i++){
ans=ans^(i*(z[i]+1));
}
cout<<ans<<endl;
l=0,r=0;
for(int i=1;i<=strlen(a+1);i++){
if(i<r) p[i]=min(z[i-l+1],r-i+1);
while(i+p[i]<=strlen(a+1)&&a[i+p[i]]==b[p[i]+1]) {
p[i]++;
}
if(r<i+p[i]-1){
r=i+p[i]-1;
l=i;
}
}
ans=0;
for(int i=1;i<=strlen(a+1);i++){
ans^=(i*(p[i]+1));
}
cout<<ans;
}
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...