[学习笔记]后缀自动机SAM
好抽象啊,早上看了两个多小时才看懂,\(\%\%\%Fading\) 早就懂了
讲解就算了吧……可以去看看其他人的博客
1、【模板】后缀自动机
\(siz\) 为该串出现的次数,\(l\) 为子串长度,每次乘一下就好了
\(Code\ Below:\)
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2000000+10;
int n,a[maxn],c[maxn],last,cnt,ch[maxn][26],fa[maxn],l[maxn],siz[maxn];
char s[maxn];ll ans;
void insert(int c){
int p=last,q=++cnt;last=q;l[q]=l[p]+1;
for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=q;
if(!p) fa[q]=1;
else {
int r=ch[p][c];
if(l[p]+1==l[r]) fa[q]=r;
else {
int s=++cnt;l[s]=l[p]+1;
memcpy(ch[s],ch[r],sizeof(ch[r]));
fa[s]=fa[r];fa[r]=fa[q]=s;
for(;ch[p][c]==r;p=fa[p]) ch[p][c]=s;
}
}
siz[q]=1;
}
int main()
{
scanf("%s",s+1);n=strlen(s+1);last=cnt=1;
for(int i=1;i<=n;i++) insert(s[i]-'a');
for(int i=1;i<=cnt;i++) c[l[i]]++;
for(int i=1;i<=cnt;i++) c[i]+=c[i-1];
for(int i=1;i<=cnt;i++) a[c[l[i]]--]=i;
for(int i=cnt;i>=1;i--){
siz[fa[a[i]]]+=siz[a[i]];
if(siz[a[i]]>1) ans=max(ans,1ll*siz[a[i]]*l[a[i]]);
}
printf("%lld\n",ans);
return 0;
}
2、SP7258 SUBLEX - Lexicographical Substring Search
题意简述:\([TJOI2015]\) 弦论找第 \(k\) 小子串 \(t=0\) 的弱化版
直接像权值线段树找第 \(k\) 小就好了
\(Code\ Below:\)
#include <bits/stdc++.h>
using namespace std;
const int maxn=200000+10;
int n,q,a[maxn],c[maxn],last,cnt,ch[maxn][26],fa[maxn],l[maxn],siz[maxn];
char s[maxn];
void insert(int c){
int p=last,q=++cnt;last=q;l[q]=l[p]+1;
for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=q;
if(!p) fa[q]=1;
else {
int r=ch[p][c];
if(l[p]+1==l[r]) fa[q]=r;
else {
int s=++cnt;l[s]=l[p]+1;
memcpy(ch[s],ch[r],sizeof(ch[r]));
fa[s]=fa[r];fa[r]=fa[q]=s;
for(;ch[p][c]==r;p=fa[p]) ch[p][c]=s;
}
}
}
int main()
{
scanf("%s",s+1);n=strlen(s+1);last=cnt=1;
for(int i=1;i<=n;i++) insert(s[i]-'a');
for(int i=1;i<=cnt;i++) c[l[i]]++;
for(int i=1;i<=cnt;i++) c[i]+=c[i-1];
for(int i=1;i<=cnt;i++) a[c[l[i]]--]=i;
for(int i=cnt;i>=1;i--){
siz[a[i]]=1;
for(int j=0;j<26;j++)
if(ch[a[i]][j]) siz[a[i]]+=siz[ch[a[i]][j]];
}
scanf("%d",&q);
int k,p;
while(q--){
scanf("%d",&k);p=1;
while(k){
for(int i=0;i<26;i++){
if(!ch[p][i]) continue;
if(siz[ch[p][i]]>=k){
putchar(i+'a');
k--;p=ch[p][i];break;
}
else k-=siz[ch[p][i]];
}
}
putchar('\n');
}
return 0;
}
3、[TJOI2015]弦论
\(t=1\) 的时候就是像模板一样记录一下 \(siz\),拓扑一遍
\(Code\ Below:\)
#include <bits/stdc++.h>
using namespace std;
const int maxn=1000000+10;
int n,q,t,a[maxn],c[maxn],last,cnt,ch[maxn][26],fa[maxn],l[maxn],siz[maxn],sum[maxn];
char s[maxn];
void insert(int c){
int p=last,q=++cnt;last=q;l[q]=l[p]+1;
for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=q;
if(!p) fa[q]=1;
else {
int r=ch[p][c];
if(l[p]+1==l[r]) fa[q]=r;
else {
int s=++cnt;l[s]=l[p]+1;
memcpy(ch[s],ch[r],sizeof(ch[r]));
fa[s]=fa[r];fa[r]=fa[q]=s;
for(;ch[p][c]==r;p=fa[p]) ch[p][c]=s;
}
}
siz[q]=1;
}
int main()
{
scanf("%s",s+1);n=strlen(s+1);last=cnt=1;
for(int i=1;i<=n;i++) insert(s[i]-'a');
for(int i=1;i<=cnt;i++) c[l[i]]++;
for(int i=1;i<=cnt;i++) c[i]+=c[i-1];
for(int i=1;i<=cnt;i++) a[c[l[i]]--]=i;
int k,p=1;
scanf("%d%d",&t,&k);
for(int i=cnt;i>=1;i--){
if(t) siz[fa[a[i]]]+=siz[a[i]];
else siz[a[i]]=1;
}
siz[1]=0;
for(int i=cnt;i>=1;i--){
sum[a[i]]=siz[a[i]];
for(int j=0;j<26;j++)
if(ch[a[i]][j]) sum[a[i]]+=sum[ch[a[i]][j]];
}
if(k>sum[1]){
printf("-1\n");
return 0;
}
while(k){
for(int i=0;i<26;i++){
if(!ch[p][i]) continue;
if(sum[ch[p][i]]>=k){
putchar(i+'a');
k-=siz[ch[p][i]];p=ch[p][i];break;
}
else k-=sum[ch[p][i]];
}
}
putchar('\n');
return 0;
}
4、SP1811 LCS - Longest Common Substring
题意:找两个串的最长公共子串
类似 \(kmp\) 一样匹配两个串,失配的话一直向上跳
\(Code\ Below:\)
#include <bits/stdc++.h>
using namespace std;
const int maxn=500000+10;
int n,a[maxn],c[maxn],last,cnt,ch[maxn][26],fa[maxn],l[maxn],ans;
char s[maxn];
void insert(int c){
int p=last,q=++cnt;last=q;l[q]=l[p]+1;
for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=q;
if(!p) fa[q]=1;
else {
int r=ch[p][c];
if(l[p]+1==l[r]) fa[q]=r;
else {
int s=++cnt;l[s]=l[p]+1;
memcpy(ch[s],ch[r],sizeof(ch[r]));
fa[s]=fa[r];fa[r]=fa[q]=s;
for(;ch[p][c]==r;p=fa[p]) ch[p][c]=s;
}
}
}
int main()
{
scanf("%s",s+1);n=strlen(s+1);last=cnt=1;
for(int i=1;i<=n;i++) insert(s[i]-'a');
scanf("%s",s+1);n=strlen(s+1);
int p=1,c,len=0;
for(int i=1;i<=n;i++){
c=s[i]-'a';
if(ch[p][c]) len++,p=ch[p][c];
else {
for(;p&&!ch[p][c];p=fa[p]);
if(p) len=l[p]+1,p=ch[p][c];
else len=0,p=1;
}
ans=max(ans,len);
}
printf("%d\n",ans);
return 0;
}
5、SP1812 LCS2 - Longest Common Substring II
题意:找多个串的最长公共子串
类似双串 \(LCS\) 一样,就是在匹配的时候记录下来长度,先取个 \(min\),再在 \(min\) 中取个 \(max\)
\(Code\ Below:\)
#include <bits/stdc++.h>
using namespace std;
const int maxn=200000+10;
int n,a[maxn],c[maxn],last,cnt,ch[maxn][26],fa[maxn],l[maxn],Max[maxn],Min[maxn],ans;
char s[maxn];
void insert(int c){
int p=last,q=++cnt;last=q;l[q]=l[p]+1;
for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=q;
if(!p) fa[q]=1;
else {
int r=ch[p][c];
if(l[p]+1==l[r]) fa[q]=r;
else {
int s=++cnt;l[s]=l[p]+1;
memcpy(ch[s],ch[r],sizeof(ch[r]));
fa[s]=fa[r];fa[r]=fa[q]=s;
for(;ch[p][c]==r;p=fa[p]) ch[p][c]=s;
}
}
}
int main()
{
scanf("%s",s+1);n=strlen(s+1);last=cnt=1;
for(int i=1;i<=n;i++) insert(s[i]-'a');
for(int i=1;i<=cnt;i++) c[l[i]]++;
for(int i=1;i<=cnt;i++) c[i]+=c[i-1];
for(int i=1;i<=cnt;i++) a[c[l[i]]--]=i;
for(int i=1;i<=cnt;i++) Min[i]=l[i];
int len,p,c;
while(scanf("%s",s+1)!=EOF){
len=0;p=1;n=strlen(s+1);
for(int i=1;i<=n;i++){
c=s[i]-'a';
if(ch[p][c]) p=ch[p][c],Max[p]=max(Max[p],++len);
else {
for(;p&&!ch[p][c];p=fa[p]);
if(p) len=l[p],p=ch[p][c],Max[p]=max(Max[p],++len);
else p=1,len=0;
}
}
for(int i=cnt;i>=1;i--){
Min[a[i]]=min(Min[a[i]],Max[a[i]]);
if(Max[a[i]]&&fa[a[i]]) Max[fa[a[i]]]=l[fa[a[i]]];
Max[a[i]]=0;
}
}
for(int i=2;i<=cnt;i++) ans=max(ans,Min[i]);
printf("%d\n",ans);
return 0;
}
6、[ZJOI2015]诸神眷顾的幻想乡
题意:问多少条本质不同路径(可以翻转)
边搜边建。因为叶子结点只有 \(20\) 个,每次就以度数为 \(1\) 的点开始搜索,建一个广义后缀自动机。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=200000+10;
int n,m,a[maxn],in[maxn],ch[maxn*20][26],fa[maxn*20],l[maxn*20],cnt;
int head[maxn],to[maxn<<1],nxt[maxn<<1],tot;ll ans;
inline int read(){
register int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return (f==1)?x:-x;
}
inline void add(int x,int y){
to[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
int insert(int c,int p){
int q=++cnt;l[q]=l[p]+1;
for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=q;
if(!p) fa[q]=1;
else {
int r=ch[p][c];
if(l[p]+1==l[r]) fa[q]=r;
else {
int s=++cnt;l[s]=l[p]+1;
memcpy(ch[s],ch[r],sizeof(ch[r]));
fa[s]=fa[r];fa[r]=fa[q]=s;
for(;p&&ch[p][c]==r;p=fa[p]) ch[p][c]=s;
}
}
return q;
}
void dfs(int x,int f,int p){
p=insert(a[x],p);
for(int i=head[x],y;i;i=nxt[i]){
y=to[i];
if(y==f) continue;
dfs(y,x,p);
}
}
int main()
{
n=read(),m=read();cnt=1;
for(int i=1;i<=n;i++) a[i]=read();
int x,y;
for(int i=1;i<n;i++){
x=read(),y=read();
add(x,y);add(y,x);
in[x]++;in[y]++;
}
for(int i=1;i<=n;i++)
if(in[i]==1) dfs(i,0,1);
for(int i=1;i<=cnt;i++)
ans+=l[i]-l[fa[i]];
printf("%lld\n",ans);
return 0;
}
7、[HEOI2016/TJOI2016]字符串
题意:\(q\) 次询问,问在 \([a,b]\) 的子串与 \([c,d]\) 的最长前缀长度
前缀不好处理,那么就干脆翻转一下好了。
最长前缀不好处理,那么就干脆二分答案一下好了。
二分后转化为存在性问题,那么就干脆套个线段树合并好了。
建一个后缀自动机,\(l\) 的长度就倍增一下。
时间复杂度 \(O(n\log^2 n)\)
\(Code\ Below:\)
#include <bits/stdc++.h>
using namespace std;
const int maxn=500000+10;
int n,m,a[maxn],b[maxn],last,cnt,ch[maxn][26],fa[maxn],l[maxn];
int pos[maxn],f[maxn][21],T[maxn],L[maxn*60],R[maxn*60],sum[maxn*60],tot;
char s[maxn];
inline int read(){
register int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return (f==1)?x:-x;
}
void insert(int c){
int p=last,q=++cnt;last=q;l[q]=l[p]+1;
for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=q;
if(!p) fa[q]=1;
else {
int r=ch[p][c];
if(l[p]+1==l[r]) fa[q]=r;
else {
int s=++cnt;l[s]=l[p]+1;
memcpy(ch[s],ch[r],sizeof(ch[r]));
fa[s]=fa[r];fa[r]=fa[q]=s;
for(;p&&ch[p][c]==r;p=fa[p]) ch[p][c]=s;
}
}
}
void pushup(int now){
sum[now]=sum[L[now]]+sum[R[now]];
}
void update(int &now,int l,int r,int x){
if(!now) now=++tot;
if(l == r){sum[now]++;return ;}
int mid=(l+r)>>1;
if(x <= mid) update(L[now],l,mid,x);
else update(R[now],mid+1,r,x);
pushup(now);
}
int merge(int x,int y,int l,int r){
if(!x||!y) return x+y;
if(l == r){sum[x]+=sum[y];return x;}
int mid=(l+r)>>1,z=++tot;
L[z]=merge(L[x],L[y],l,mid);
R[z]=merge(R[x],R[y],mid+1,r);
pushup(z);
return z;
}
int query(int now,int Le,int Ri,int l,int r){
if(!now) return 0;
if(Le <= l && r <= Ri) return sum[now];
int mid=(l+r)>>1,ans=0;
if(Le <= mid) ans+=query(L[now],Le,Ri,l,mid);
if(Ri > mid) ans+=query(R[now],Le,Ri,mid+1,r);
return ans;
}
int check(int len,int x,int L,int R){
for(int i=20;i>=0;i--)
if(f[x][i]&&l[f[x][i]]>=len) x=f[x][i];
return query(T[x],L+len-1,R,1,n);
}
int main()
{
n=read(),m=read();
scanf("%s",s+1);last=cnt=1;
reverse(s+1,s+n+1);
for(int i=1;i<=n;i++){
insert(s[i]-'a');pos[i]=last;
update(T[pos[i]],1,n,i);
}
for(int i=1;i<=cnt;i++) b[l[i]]++;
for(int i=1;i<=cnt;i++) b[i]+=b[i-1];
for(int i=1;i<=cnt;i++) a[b[l[i]]--]=i;
for(int i=cnt;i>=1;i--)
if(fa[a[i]]) T[fa[a[i]]]=merge(T[fa[a[i]]],T[a[i]],1,n);
for(int i=1;i<=cnt;i++) f[i][0]=fa[i];
for(int j=1;j<=20;j++)
for(int i=1;i<=cnt;i++) f[i][j]=f[f[i][j-1]][j-1];
int a,b,c,d,l,r,mid,ans;
while(m--){
a=n-read()+1,b=n-read()+1,c=n-read()+1,d=n-read()+1;
l=0,r=min(a-b+1,c-d+1),ans=0;
while(l<=r){
mid=(l+r)>>1;
if(check(mid,pos[c],b,a)) l=mid+1,ans=mid;
else r=mid-1;
}
printf("%d\n",ans);
}
return 0;
}
8、[HAOI2016]找相同字符
题意:问两个串的公共子串个数
如核心代码 \((l[i]-l[fa[i]])\times siz[i]\)
for(int i=2;i<=cnt;i++) sum[a[i]]=sum[fa[a[i]]]+(ll)(l[a[i]]-l[fa[a[i]]])*siz[a[i]];
scanf("%s",s+1);n=strlen(s+1);
int len=0,p=1,c;
for(int i=1;i<=cnt;i++){
c=s[i]-'a';
for(;p&&!ch[p][c];p=fa[p]);
if(!p) p=1,len=0;
else {
len=min(len,l[p])+1;p=ch[p][c];
ans+=sum[fa[p]]+(ll)(len-l[fa[p]])*siz[p];
}
}
\(Code\ Below:\)
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=400000+10;
int n,a[maxn],b[maxn],last,cnt,ch[maxn][26],fa[maxn],l[maxn],siz[maxn];
char s[maxn];ll sum[maxn],ans;
void insert(int c){
int p=last,q=++cnt;last=q;l[q]=l[p]+1;
for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=q;
if(!p) fa[q]=1;
else {
int r=ch[p][c];
if(l[p]+1==l[r]) fa[q]=r;
else {
int s=++cnt;l[s]=l[p]+1;
memcpy(ch[s],ch[r],sizeof(ch[r]));
fa[s]=fa[r];fa[r]=fa[q]=s;
for(;p&&ch[p][c]==r;p=fa[p]) ch[p][c]=s;
}
}
siz[q]=1;
}
int main()
{
scanf("%s",s+1);n=strlen(s+1);last=cnt=1;
for(int i=1;i<=n;i++) insert(s[i]-'a');
for(int i=1;i<=cnt;i++) b[l[i]]++;
for(int i=1;i<=cnt;i++) b[i]+=b[i-1];
for(int i=cnt;i>=1;i--) a[b[l[i]]--]=i;
for(int i=cnt;i>=1;i--) siz[fa[a[i]]]+=siz[a[i]];
for(int i=2;i<=cnt;i++) sum[a[i]]=sum[fa[a[i]]]+(ll)(l[a[i]]-l[fa[a[i]]])*siz[a[i]];
scanf("%s",s+1);n=strlen(s+1);
int len=0,p=1,c;
for(int i=1;i<=cnt;i++){
c=s[i]-'a';
for(;p&&!ch[p][c];p=fa[p]);
if(!p) p=1,len=0;
else {
len=min(len,l[p])+1;p=ch[p][c];
ans+=sum[fa[p]]+(ll)(len-l[fa[p]])*siz[p];
}
}
printf("%lld\n",ans);
return 0;
}
[学习笔记]后缀自动机SAM的更多相关文章
- [笔记] 后缀自动机 (SAM)
实现 void ins(int c){ int np = ++dcnt, p = lst; lst = np; t[np].len = t[p].len + 1, t[np].eps = 1; whi ...
- 后缀自动机SAM学习笔记
前言(2019.1.6) 已经是二周目了呢... 之前还是有一些东西没有理解到位 重新写一下吧 后缀自动机的一些基本概念 参考资料和例子 from hihocoder DZYO神仙翻译的神仙论文 简而 ...
- [转]后缀自动机(SAM)
原文地址:http://blog.sina.com.cn/s/blog_8fcd775901019mi4.html 感觉自己看这个终于觉得能看懂了!也能感受到后缀自动机究竟是一种怎样进行的数据结构了. ...
- SPOJ 1811. Longest Common Substring (LCS,两个字符串的最长公共子串, 后缀自动机SAM)
1811. Longest Common Substring Problem code: LCS A string is finite sequence of characters over a no ...
- 后缀自动机(SAM)奶妈式教程
后缀自动机(SAM) 为了方便,我们做出如下约定: "后缀自动机" (Suffix Automaton) 在后文中简称为 SAM . 记 \(|S|\) 为字符串 \(S\) 的长 ...
- 【算法】后缀自动机(SAM) 初探
[自动机] 有限状态自动机的功能是识别字符串,自动机A能识别字符串S,就记为$A(S)$=true,否则$A(S)$=false. 自动机由$alpha$(字符集),$state$(状态集合),$in ...
- 浅谈后缀自动机SAM
一下是蒟蒻的个人想法,并不很严谨,仅供参考,如有缺误,敬请提出 参考资料: 陈立杰原版课件 litble 某大神 某大神 其实课件讲得最详实了 有限状态自动机 我们要学后缀自动机,我们先来了解一下自动 ...
- 后缀自动机(SAM) 学习笔记
最近学了SAM已经SAM的比较简单的应用,SAM确实不好理解呀,记录一下. 这里提一下后缀自动机比较重要的性质: 1,SAM的点数和边数都是O(n)级别的,但是空间开两倍. 2,SAM每个结点代表一个 ...
- SAM学习笔记&AC自动机复习
形势所迫,一个对字符串深恶痛绝的鸽子又来更新了. SAM 后缀自动机就是一个对于字符串所有后缀所建立起的自动机.一些优良的性质可以使其完成很多字符串的问题. 其核心主要在于每个节点的状态和$endpo ...
随机推荐
- 如何在Eclipse下安装SVN插件——subclipse
如何在Eclipse下安装SVN插件——subclipse | 浏览:2799 | 更新:2014-09-20 22:39 1 2 3 4 5 6 分步阅读 版本控制是开发人员必不可少的工具,而SVN ...
- IT资产管理详解
- IOS tableView的一些问题总结
1.与用户的交互的开启和关闭 tableView.userInteractionEnabled = NO; 2.TableView的Group样式中,默认的每个section都有sectionHe ...
- C++STL list
list双向链表 高效进行插入删除数据 不可以随机存取元素,所以不支持at()和[]操作符.it可以++ --,不能it+5 节点序号从0开始 list<int> l; l.push_b ...
- Attach()和Detach()函数
一.Windows对象和MFC对象的区别? MFC对象实际上并没有把整个Windows对象都包装在其中.对于窗口:MFC对象它只是有一个窗口句柄而已,这个窗口句柄如果指向一个实际存在的窗口对象(窗口对 ...
- Codeforces Round #298 (Div. 2)--D. Handshakes
#include <stdio.h> #include <algorithm> #include <set> using namespace std; #defin ...
- request.getScheme() 使用方法
今天在看代码时,发现程序使用了 request.getScheme() .不明白是什么意思,查了一下.结果整理如下: 1.request.getScheme() 返回当前链接使用的协议:一般应用返回h ...
- hdu 5087 次长升序串的长度
http://acm.hdu.edu.cn/showproblem.php?pid=5087 求数列次长升序串的长度 还是dp求最长升序串的长度,再开一个数组记录对于dp值的串的个数 最后看一下最长字 ...
- SRM474
250pt 题意:在一个N维的空间里,有一个人开始在原点,现在给出N<=50个指令序列,每个指令序列为某一维+1或者减一,问是否经过某个点至少2次. 思路:操作很小,直接模拟判断即可 code: ...
- PL/SQL客户端连Oracle很快就断开问题的解决
PL/SQL登录很短时间session就自动断开 1.首先查看你这个用户的profile文件 select profile from dba_users where username='USERNAM ...