后缀数组
参考博文0
参考博文1
例题
代码可参考参考博文1
感觉主要就是递增+桶排序(桶排序我一直以为从高位到低位排序,错了好久。。。)原理不难,但手撕代码太难了。
求height主要根据
具体证明有空研究。
后缀自动机
其实参考博客里面的一些结论的证明过程对使用并没有影响。
主要记住,结点本质上就是endpos的一个集合
边就是字符。
代码来自参考博客
struct NODE
{
int ch[26];
int len,fa;
NODE(){memset(ch,0,sizeof(ch));len=0;}
}dian[MAXN<<1];
int las=1,tot=1;
void add(int c)
{
int p=las;int np=las=++tot;
dian[np].len=dian[p].len+1;
for(;p&&!dian[p].ch[c];p=dian[p].fa)dian[p].ch[c]=np;
if(!p)dian[np].fa=1;//以上为case 1
else
{
int q=dian[p].ch[c];
if(dian[q].len==dian[p].len+1)dian[np].fa=q;//以上为case 2
else
{
int nq=++tot;dian[nq]=dian[q];
dian[nq].len=dian[p].len+1;
dian[q].fa=dian[np].fa=nq;
for(;p&&dian[p].ch[c]==q;p=dian[p].fa)dian[p].ch[c]=nq;//以上为case 3
}
}
}
char s[MAXN];int len;
int main()
{
scanf("%s",s);len=strlen(s);
for(int i=0;i<len;i++)add(s[i]-'a');
}
dian[i].len为endpos在该集合内的最长子串长度,dian[i].ch类似AC自动机里面的指针,dian[i].fa可看作parent树的父节点。
在后缀自动机里面,parent树的边并不是真实存在的。
add函数的前三行可看作对新加入的点连实际存在的边。每加入一个点,可看作加入了一个endpos集合(至少存在一个元素,即长度为m的前缀所对应的endpos)。首先要与前一个点构造一条新边(注意不是case3里面造出来的点),然后就是这个点的父节点、爷爷节点等,直到某个节点的ch[j]!=0。
接下来都是维护parent树。
case1是直接和根节点连在一起。
case2是根据建边是找到的ch[j]!=0的点,不过为啥q点就是父节点我没仔细想。
case3就是新加一个点,那个for循环貌似是向上找fa,自立门户。
具体还是太难懂了,不过感觉这东西和AC自动机一样,可以当板子套(代码量还比AC自动机小很多 )
AC自动机主要解决多模式串匹配问题,后缀自动机感觉主要解决子串最值问题。
最后生成的这个东西
(图片来自参考博客)图最上侧的8应为9
蓝线为parent树的线,(parent树中,父节点的子节点所代表的集合并中元素的数目小于父节点元素的个数)。对于黑线,我们可以看作有一条主干道(能表示出整个字符串的线),上图主干道开头为0,末尾为8,从0出发总有一条黑线组成的路径可以表示某一后缀,因此从0出发跑dfs一定可以跑出任何一个子串。非主干道的黑线可能会在构造过程中消失。
想统计某一子串出现次数时,直接在parent树上做DP就可以
应用
B站貌似只有一个讲后缀自动机的视频,感觉这么有名的东西居然没有教程。
P3975 [TJOI2015]弦论
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#define ll long long
using namespace std;
const int N=1100000;
char s[N];
int fa[N],len[N],siz[N],ch[N][26];
int t[N],A[N];
ll sum[N],K;
int l,lst=1,node=1,T;
void Extend(int c)
{
int f=lst,p=++node;lst=p;
len[p]=len[f]+1;siz[p]=1;
while(f&&!ch[f][c]) ch[f][c]=p,f=fa[f];
if(!f) {fa[p]=1;return;}
int x=ch[f][c],y=++node;
if(len[x]==len[f]+1) {fa[p]=x;node--;return;}
memcpy(ch[y],ch[x],sizeof(ch[y]));
len[y]=len[f]+1; fa[y]=fa[x]; fa[x]=fa[p]=y;
while(f&&ch[f][c]==x) {ch[f][c]=y;f=fa[f];}
}
void Print(int x,int k)
{
if(k<=siz[x]) return;
k-=siz[x];
for(int i=0;i<26;i++)
{
int R=ch[x][i]; if(!R) continue;
if(k>sum[R]) {k-=sum[R];continue;}
putchar(i+'a');Print(R,k);return;
}
}
int main()
{
//Part 1 Build SAM
scanf("%s%d%lld",s,&T,&K);l=strlen(s);
for(int i=l;i>=1;i--) s[i]=s[i-1];s[0]=0;
for(int i=1;i<=l;i++) Extend(s[i]-'a');//类似上文add()函数
//Part 2 Sort
for(int i=1;i<=node;i++) t[len[i]]++;
for(int i=1;i<=node;i++) t[i]+=t[i-1];//类似SA的按长度排序
for(int i=1;i<=node;i++) A[t[len[i]]--]=i;
//上面三行只是单纯的按长度排序,由于parent树的父节点的编号可能比
//子节点小(case3)
for(int i=node;i>=1;i--) siz[fa[A[i]]]+=siz[A[i]];
for(int i=1;i<=node;i++) T==0?(sum[i]=siz[i]=1):(sum[i]=siz[i]);
siz[1]=sum[1]=0;
/*
这一段代码调试了半个小时
前者是对自动机处理(自动机上累加求的是子串个数)
后者是parent树(parent树上累加求的是i节点对应的endpos的字符集的longest的出现次数)
*/
for(int i=node;i>=1;i--)
for(int j=0;j<26;j++)
if(ch[A[i]][j]) sum[A[i]]+=sum[ch[A[i]][j]];
// for(int i=node;i>=1;i--) sum[fa[A[i]]]+=sum[A[i]];
if(sum[1]<K) puts("-1");
else Print(1,K),puts("");
return 0;
}
HDU - 6138
题目大意:给n个串,有m次询问,每次询问输入a,b序号。问a,b的公共子串中,既是x,y的公共子串又是其他某一串的前缀串的最长串长度
代码:
自己做的时候用了SAM:
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
#include<string>
using namespace std;
#define maxn 100010
#define mod 100000007
typedef long long ll;
int n, m, T, a, b, slen[maxn];
vector<ll>hs[maxn];
string ss[maxn];
int aw[maxn], bw[maxn];
struct NODE
{
int ch[26];
int len, fa;
void init()
{
memset(ch, 0, sizeof(ch));
len = fa = 0;
}
}dian[maxn << 1];
int las = 1, tot = 1;
void add(int c)
{
int p = las; int np = las = ++tot;
dian[np].len = dian[p].len + 1;
for (; p && !dian[p].ch[c]; p = dian[p].fa)dian[p].ch[c] = np;
if (!p)dian[np].fa = 1;//以上为case 1
else
{
int q = dian[p].ch[c];
if (dian[q].len == dian[p].len + 1)dian[np].fa = q;//以上为case 2
else
{
int nq = ++tot; dian[nq] = dian[q];
dian[nq].len = dian[p].len + 1;
dian[q].fa = dian[np].fa = nq;
for (; p&&dian[p].ch[c] == q; p = dian[p].fa)dian[p].ch[c] = nq;//以上为case 3
}
}
}
int dfs(string str)
{
int cur = 0, now = 1;
for (auto &x : str)
{
int v = x - 'a';
if (dian[now].ch[v]) cur++, now = dian[now].ch[v];
else return cur;
}
return cur;
}
void solve()
{
int i;
las = 1; tot = 1;
int len = ss[a].length();
for (i = 1; i <= 2 * len + 1; i++) dian[i].init();
for (int i = 0; i < len; i++)add(ss[a][i] - 'a');
for (i = 1; i <= n; i++)
{
aw[i] = dfs(ss[i]);
}
las = 1; tot = 1;
len = ss[b].length();
for (i = 1; i <= 2 * len + 1; i++) dian[i].init();
for (i = 0; i < len; i++)add(ss[b][i] - 'a');
for (i = 1; i <= n; i++)
{
bw[i] = dfs(ss[i]);
}
int ans = 0;
for (i = 1; i <= n; i++)
{
ans = max(ans, min(aw[i], bw[i]));
}
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(false);
int i, j;
cin >> T;
while (T--)
{
cin >> n;
for (i = 1; i <= n; i++)
{
cin >> ss[i];
}
cin >> m;
for (i = 1; i <= m; i++)
{
cin >> a >> b;
solve();
}
}
return 0;
}
但看网上大神基本都用的AC自动机
参考博客
截图自参考博客