SAM 简记
SAM 可以表示一个字符串的所有子串。表示的意思是可以用从起点到某一点的路径表达。
SAM 是自动机,若它能表示一个字符串,则显然可以表示这个字符串的前缀。
所以,只要表示出所有的后缀,就能表示所有子串。
对于 的一个子串 , 表示其在 出现的结束位置集合。例如 时,。
显然,若两个子串的 有交,说明一个字符串出现的地方就有另一个字符串,所以其中一个是另一个的后缀,故两个 有包含关系。
所以,两个子串的 要么没有交,要么有包含关系。
相同的子串中,长度一定是一段连续的数字。因为其中最长的子串的 被比它短的包含,最短的子串的又包含所有比它长的子串,所以对于 相等的长度为 的两个子串,一定有长度为 的子串的 与之相等。
我们可以把 相同的子串归为一类。在这一类中最长的字符串前面加一个字符,其 不同,但是被包含。加不同的字符可能会导出不同的 ,根据上面所述,它们没有交。所以可以通过在最长的字符前面加字符的方式拆分 集合。当然,不是所有集合中的数字都一定会保留下来。
所以我们可以看出一个树结构:根节点代表 的一类,这一类长度最小的是空串。然后可以通过向最长字符串前加字符的方式拆分成几个集合。我们叫这棵树 parent tree。
parent tree 的节点和 SAM 的节点意义一致(但是边不一致),所以我们可以把一个节点当做一个状态。其中 包含 的链上的点被称为终止状态,包含了 的后缀。在这棵树上从儿子到父亲的边被称为后缀链接,用 表示,可以跳到自己的后缀。用 表示状态 中的最长字符串,。
显然,状态 中最短字符串的长度为 。
而 SAM 则用转移将点连接起来。转移是在 的 后面 添加字符得到的字符串对应的另一状态。
构造 SAM 的方式也是将字符一个个加入。对于每个状态,我们保存转移、、。初始时只有一个状态,编号为 。
int cur=++tot,u=lst;
len[cur]=len[lst]+1;lst=cur;
for(;~u&&!t[u][c];u=f[u])t[u][c]=cur;
if(u==-1)return f[cur]=0,void();
从原来表示整个字符串的状态开始,不断跳后缀链接,直到有 的转移。没有 的转移时,将其连接到 。 的意义是加入这个字符后表示整个字符串的状态。一旦有 转移,显然其后缀也必然有。f
表示后缀链接。若跳出去了,表明新字符串在老字符串中没有后缀,直接赋值为 0。否则,。
int v=t[u][c];
if(len[u]+1==len[v])return f[cur]=v,void();
如果状态 恰好满足这个条件,说明 。
int p=++tot;len[p]=len[u]+1;f[p]=f[v];
memcpy(t[p],t[v],sizeof(t[p]));
for(;~u&&t[u][c]==v;u=f[u])t[u][c]=p;
f[v]=f[cur]=p;
否则必须将 拆成两个点,即将 及其后缀分入 ,其他的留在 ,另一个点 复制 除了 的信息。造出了我们需要的 后,需要把 后面的所以最后将 及其后缀到 上的转移调整到 ,。
对于时间复杂度,我不懂。也许以后会补上。
做 P3804 【模板】后缀自动机 (SAM) 时还有个问题就是计算每个点的 的大小。可以把每个可能的元素即 预先放入节点中。具体地,把每个前缀对应的状态的 加一,然后遍历 parent tree 得出大小。
#include<bits/stdc++.h>
using namespace std;
constexpr int N=2e6+5;
int tot,lst,f[N],t[N][26],len[N],siz[N];
vector<int>e[N];long long ans;
void dfs(int u){
for(int v:e[u])dfs(v),siz[u]+=siz[v];
if(u&&siz[u]>1)ans=max(ans,1ll*siz[u]*len[u]);
}
int main(){
f[0]=-1;string s;cin>>s;
for(int c:s){
int cur=++tot,u=lst;c-='a';siz[cur]++;
len[cur]=len[lst]+1;lst=cur;
for(;~u&&!t[u][c];u=f[u])t[u][c]=cur;
if(u==-1){f[cur]=0;continue;}
int v=t[u][c];
if(len[v]==len[u]+1)f[cur]=v;else{
int p=++tot;len[p]=len[u]+1;f[p]=f[v];
memcpy(t[p],t[v],sizeof(t[p]));
for(;~u&&t[u][c]==v;u=f[u])t[u][c]=p;
f[v]=f[cur]=p;
}
}
for(int i=1;i<=tot;i++)e[f[i]].push_back(i);
dfs(0);cout<<ans<<'\n';
return 0;
}
SAM 可以理解为对一个字符串的所有子串建立的 ACAM。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现