题目链接:https://www.luogu.com.cn/problem/CF700E
给出一个字符串S,求一个最大的k使得存在k个字符串其中s1是S的子串,si+1在si中出现了至少2次。
首先我们需要有两个结论
- si+1一定是si的其中一个后缀。因为如果si+1不是si的一个后缀,那么si去掉后面那一部分不会影响匹配数并且更短,也就是更优
- 对于parents树上的一对父子x,y,y代表的所以字符串与x最长串的匹配数均相等。因为如果有不等的,那么证明y中的字符串的出现endpos集合不同,不符合SAM的定义,故不成立。
这样我们就可以在SAM上进行dp了,因为第一个结论我们可以直接在fail树上dp,然后第二个结论让我们能够使用每个节点最长的串来进行匹配,因为这不会影响答案。
现在我们需要考虑如何判断一个节点的串是否在另一个它的祖先节点的串中出现了两次,首先作为后缀已经出现了一次,然后只需要判断是否包含一个出现在[posx−lenx+leny,posx−1]的串就好了,因为endpos在这个范围内出现的串一定是与字符串x相同的。
用线段树合并维护每个节点包含的串的位置就好了,还有就是要从上往下转移。
时间复杂度O(nlogn)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=4e5+10,M=N<<6;
int n,cnt,last,ans,c[N],p[N],f[N],top[N];
int rt[N],pos[N],len[N],fa[N],ch[N][26];
char s[N];
struct Seq_Tree{
int w[M],ls[M],rs[M],cnt;
int Change(int x,int L,int R,int pos){
int y=++cnt;w[y]=w[x]+1;
if(L==R)return y;
int mid=(L+R)>>1;
if(pos<=mid)ls[y]=Change(ls[x],L,mid,pos),rs[y]=rs[x];
else ls[y]=ls[x],rs[y]=Change(rs[x],mid+1,R,pos);
return y;
}
int Merge(int x,int y,int L,int R){
if(!x||!y)return x|y;
int p=++cnt;w[p]=w[x]+w[y];
if(L==R)return p;
int mid=(L+R)>>1;
ls[p]=Merge(ls[x],ls[y],L,mid);
rs[p]=Merge(rs[x],rs[y],mid+1,R);
w[p]=w[ls[p]]+w[rs[p]];
return p;
}
int Ask(int x,int L,int R,int l,int r){
if(!x)return 0;
if(L==l&&R==r)return w[x];
int mid=(L+R)>>1;
if(r<=mid)return Ask(ls[x],L,mid,l,r);
if(l>mid)return Ask(rs[x],mid+1,R,l,r);
return Ask(ls[x],L,mid,l,mid)+Ask(rs[x],mid+1,R,mid+1,r);
}
}T;
void Insert(int c){
int p=last,np=last=++cnt;
len[np]=len[p]+1;
for(;p&&!ch[p][c];p=fa[p])ch[p][c]=np;
if(!p)fa[np]=1;
else{
int q=ch[p][c];
if(len[p]+1==len[q])fa[np]=q;
else{
int nq=++cnt;len[nq]=len[p]+1;
memcpy(ch[nq],ch[q],sizeof(ch[nq]));
fa[nq]=fa[q];pos[nq]=pos[q];fa[q]=fa[np]=nq;
for(;p&&ch[p][c]==q;p=fa[p])ch[p][c]=nq;
}
}
return;
}
int main()
{
scanf("%d",&n);cnt=last=1;
scanf("%s",s+1);
for(int i=1;i<=n;i++)
Insert(s[i]-'a'),rt[last]=T.Change(rt[last],1,n,i),pos[last]=i;
for(int i=1;i<=cnt;i++)c[len[i]]++;
for(int i=1;i<=n;i++)c[i]+=c[i-1];
for(int i=1;i<=cnt;i++)p[c[len[i]]--]=i;
for(int i=cnt;i>1;i--)
rt[fa[p[i]]]=T.Merge(rt[fa[p[i]]],rt[p[i]],1,n);
int ans=1;
for(int i=2;i<=cnt;i++){
int x=p[i],y=fa[x];
if(y==1){f[x]=1;top[x]=x;continue;}
y=top[y];
if(T.Ask(rt[y],1,n,pos[x]-len[x]+len[y],pos[x]-1))
f[x]=f[y]+1,top[x]=x;
else top[x]=y,f[x]=f[y];
ans=max(ans,f[x]);
}
printf("%d\n",ans);
return 0;
}
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构