持续更新qwq

KMP

其实是MP啦qwq

就是先自己匹配自己得到状态图,然后再在上面进行模式串的匹配。

nxt数组返回的是以该节点结尾的,最长的,在前面出现过的,不相交的,字符串的最靠右的,末位位置。

举个例子:对于字符串aabaabaabaab来说,它的nxt数组是这个样子的——

nxt[0]=0,nxt[1]=0,nxt[2]=1,nxt[3]=0,nxt[4]=1,nxt[5]=2,nxt[6]=3,nxt[7]=4,nxt[8]=5,nxt[9]=6,nxt[10]=7,nxt[11]=8

以下是模板啦qwq

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define MAXN 1000010
using namespace std;
int nxt[MAXN],kmp[MAXN];
char s1[MAXN],s2[MAXN];
int main()
{
cin>>(s1+1);
cin>>(s2+1);
int len1=strlen(s1+1),len2=strlen(s2+1);
int j=0;
for(int i=2;i<=len2;i++)
{
while(j&&s2[j+1]!=s2[i]) j=kmp[j];
if(s2[j+1]==s2[i]) j++;
kmp[i]=j;
}
j=0;
for(int i=1;i<=len1;i++)
{
while(j&&s2[j+1]!=s1[i]) j=kmp[j];
if(s2[j+1]==s1[i]) j++;
if(j==len2)
{
printf("%d\n",i-len2+1);
j=kmp[j];
}
}
for(int i=1;i<=len2;i++) printf("%d ",kmp[i]);
return 0;
}

SA后缀数组

以下是蒟蒻自己写的注释的模板qwq(没有height数组)(输入一个字符串,依次输出排名为i的字符串所在位置)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define MAXN 1000010
using namespace std;
int n,m,p;
int tax[MAXN],rnk[MAXN],tp[MAXN],sa[MAXN];
char s[MAXN];
inline void qsort()
{
for(int i=1;i<=m;i++) tax[i]=0;
//tax[i]表示排名为i的后缀的个数
for(int i=1;i<=n;i++) tax[rnk[i]]++;//累加排名为rnk[i]的个数
for(int i=1;i<=m;i++) tax[i]+=tax[i-1];//求前缀和
for(int i=n;i>=1;i--) sa[tax[rnk[tp[i]]]--]=tp[i];
//现在tp数组和rnk数组已经有序了,所以我们现在要用它们来更新sa
}
inline void suffixsort()
{
m=75,p=0;
//m是字符集的个数,下文中用p来计数
for(int i=1;i<=n;i++) rnk[i]=s[i]-'0'+1,tp[i]=i;
qsort();
for(int w=1;p<n;w<<=1,m=p)
{
p=0;
//一定要记得p的清零,现在它是一个计数的作用
for(int i=1;i<=w;i++) tp[++p]=n-w+i;//现在处理的是后面不能配对的位置的第二关键字的排名
//这些位置的第二关键字为0,都相等。但是为什么++p了,因为这里计算的是第二关键字的个数
for(int i=1;i<=n;i++) if(sa[i]>w) tp[++p]=sa[i]-w;//现在处理的可以配对的第二关键字的排名
qsort();
swap(tp,rnk);
//我们要用上个rnk更新现在rnk
rnk[sa[1]]=p=1;
//初始化
for(int i=2;i<=n;i++) rnk[sa[i]]=(tp[sa[i]]==tp[sa[i-1]])&&(tp[sa[i]+w]==tp[sa[i-1]+w])?p:++p;
//当第一,第二关键字的排名相同的时候 显然现在的rnk也是一样的
//现在p表示的是不同排名的个数,如果p==n就可以结束了
}
}
int main()
{
freopen("ce.in","r",stdin);
scanf("%s",s+1);
n=strlen(s+1);
suffixsort();
for(int i=1;i<=n;i++) printf("%d ",sa[i]);
return 0;
}

SAM后缀自动机

以下内容摘抄自这里

inline void extend(int c)
{
int p=last,np=++tot;last=np;
t[np].len=t[p].len+1;
while(p&&!t[p].son[c]) t[p].son[c]=np,p=t[p].ff;
if(!p)t[np].ff=1;
else
{
int q=t[p].son[c];
if(t[p].len+1==t[q].len) t[np].ff=q;
else
{
int nq=++tot;
t[nq]=t[q];
t[nq].len=t[p].len+1;
t[q].ff=t[np].ff=nq;
while(p&&t[p].son[c]==q) t[p].son[c]=nq,p=t[p].ff;
}
}
}
  • endpos

endpos是一个子串结束为止组成的集合。

对于所有结束位置相同的字串,也就是endpos相同的两个子串。他们一个一定是另一个的后缀

两个字符串如果有一个是另一个的后缀,那么较长串的后缀一定是较短串的endpos的子集

两个字符串如果没有后缀的关系,那么他们的endpos的交集一定是空集

后缀自动机的每个节点是依照endpos来划分的,对于endpos相同的子串,我们可以划分在一起。所以我们不难得出一点,对于一堆endpos相同的子串,他们一定互为后缀,并且他们的长度连续。

既然后缀连续,那就一定有一个最长的串,不妨记为longest。那么,所有的其他串一定是它的后缀。随着后缀长度的减小,那么从某一个后缀开始,就可能出现在了更多的位置。那么买这个后缀以及比它更短的后缀的endpos一定会变大,此时他们就会分到别的节点去了。

确定了endpos和长度len就能确定唯一的子串

  • trans

trans是转移的意思,设trans(s,c)表示当前在s状态,接受一个字符c之后所到达的状态。一个状态s表示若干endpos相同的连续子串。

那么此时相当于在后面加上了一个字符c。那么我们对于任意一个串直接加上一个字符c之后,组成的串的endpos还是相同的。所以trans(s,c)就会指向这个状态。

  • parent/suffix links

不妨设一个状态中包含的最短的串叫做shortest。那么我们就知道shortest的任意一个非自己的后缀一定就会出现在了更多的位置。它的那个最长的后缀,也就是减去了第一个字符后的串,就会出现在另外一个状态里,并且是那个状态的longest。

parent tree上,每一个点集的父亲都是自己的后缀。也就是说,沿着suffix_link向上跳,会一直跳到自己的后缀(不过当然,它们不在一个endpos里面)

假设当前状态为s,\(s.shortest.len=parent.longest.len+1\)

所以对于每个状态,没有必要记录shortest,因为你只要知道parent就可以算出来了。

s的endpos是parent的子集

这个不难证明,因为parent包含了更多的位置。

如果trans(s,c)不等于NULL,那么trans(parent,c)不等于NULL

parent是一个完全包含了s的状态,也正因为如此,parent的endpos就是所有儿子endpos的并集

将所有的parent翻过来,我们就得到了parent树。如果要处理什么,就需要parent树的拓扑序。(因为parent相当于包含了它的所有子树,都需要更新上去)。。。不过其实不需要拓扑排序,我们知道s的endpos完全被parent的endpos包含,所以s.longest一定长于parent.longest。所以一个状态的longest越长,它一定要被更先访问。所以,按照longest的长度进行桶排序就可以解决拓扑序了。

  • extend

    对于一个SAM的构造,我们依次加入字符c,来进行构造。

假设原来的字符串是T,首先一定会有一个新节点。因为新加入了一个字符后,一定出现了这个新的字符T+c。此时的endpos一定是新的位置。同时,原来的T的最后一个位置也可以通过+c变到这个新位置。设原来的最后一个位置的状态是last,新的状态是np。所以tans(last,c)=np。

根据前面的东西,我们知道last的祖先们一定也会有这个trans,之后来解决这个问题——

令p=last,一直沿着parent往前跳,也就是不断令\(p=p.parent\),所以p所代表的的,就是越来越短的T的后缀。因为要更新的是最后的位置。只有当存在T的最后一个位置的时候才能更新。

如果\(trans(p,c)=NULL\),直接令\(tans(p,c)=np\)。很显然是可以在后面添加一个c到达np的,如果跳完之后发现没有parent了,直接把np.parent指向1(也就是空串所代表的状态)

如果某个\(trans(p,c)\)不等于NULL,那么设\(q=trans(p,c)\)——

如果有\(longest(p)+1=longest(q)\),那么我们在p的串后面直接添上一个c之后就是q状态。没有任何问题,直接在作为T的后缀的那一个子串上,直接添加一个x显然也可以到达q状态,又因为np所代表的endpos更小,所以\(np.parent=q\)。

否则的话,也就是\(longest(q)>longest(p)+1\)(也就是前面那个while更新的时候可能会导致的情况)。如果直接插入的话,相当于给q的endpos强行插入一个np,但是我们发现,如果强行插入进去,这个T+c的后缀会出现在更多的位置,应该属于另外一个状态,不太行。

所以我们新建一个节点nq,相当于把q拆成两部分:一部分是T+c的那个后缀,一个是\(longest(p)+c\),也就是\(longest(nq)=longest(p)+1\),显然T+c的后缀是包含了状态较少的,拆分出来的一部分q是长度较长的。所以\(q.parent=np.parent=nq\)。同时,继续沿着p的parent往上走,把所有的q都替换成nq。

也就是这样——

SAM里面有两种节点,一种是直接建出来,另外一种是分裂出来的。

last表示的是新建的那个节点。

一个节点可以表示一个类,里面有很多子串,他们的 endpos相同。

比如对于字符串S="aabbabd",它的后缀自动机是:

至于空间问题,开处理的字符串长度的两倍就行了qwq

如果只要求解right集合大小的话,直接基数排序一下,按照拓扑序向上合并即可。

如果要求right集合,线段树向上合并维护。

两个后缀的最长公共前缀是他们在parent tree上面LCA的len

两个前缀的最长公共后缀是他们在后缀树上面LCA的len

OI字符串 简单学习笔记的更多相关文章

  1. OI数学 简单学习笔记

    基本上只是整理了一下框架,具体的学习给出了个人认为比较好的博客的链接. PART1 数论部分 最大公约数 对于正整数x,y,最大的能同时整除它们的数称为最大公约数 常用的:\(lcm(x,y)=xy\ ...

  2. OI图论 简单学习笔记

    网络流另开了一个专题,所以在这里就不详细叙述了. 图 一般表示为\(G=(V,E)\),V表示点集,E表示边集 定义图G为简单图,当且仅当图G没有重边和自环. 对于图G=(V,E)和图G2=(V2,E ...

  3. OI网络流 简单学习笔记

    持续更新! 基本上只是整理了一下框架,具体的学习给出了个人认为比较好的博客的链接. ..怎么说呢,最基础的模板我就我不说了吧qwq,具体可以参考一下这位大佬写的博客:最大流,最小割,费用流 费用流 跑 ...

  4. OI计算几何 简单学习笔记

    学习平面几何,首先我们要会熟练地应用向量,其次也要知道一些基本的几何知识.(其实看看数学课本就可以了吧) 因为是看的蓝书,所以很多东西做了引用.(update:还参考了赵和旭dalao的讲义) 下面先 ...

  5. OI多项式 简单学习笔记

    咕咕咕 先开个坑(其实是存模板来了) 一些特别简单的前置东西qwq 复数的计算 复数相加:向量相加,复数相乘.复数相乘:模长相乘,旋转量相加(就是复平面坐标轴逆时针旋转的角度) (当然也可以直接使用c ...

  6. Log4j简单学习笔记

    log4j结构图: 结构图展现出了log4j的主结构.logger:表示记录器,即数据来源:appender:输出源,即输出方式(如:控制台.文件...)layout:输出布局 Logger机滤器:常 ...

  7. Linux——帮助命令简单学习笔记

    Linux帮助命令简单学习笔记: 一: 命令名称:man 命令英文原意:manual 命令所在路径:/usr/bin/man 执行权限:所有用户 语法:man [命令或配置文件] 功能描述:获得帮助信 ...

  8. <<C++标准程序库>>中的STL简单学习笔记

    0. 内容为个人学习笔记, 仅供参考, 如有错漏, 欢迎指正! 1. STL中的所有组件都是由模板构成的, 所以其元素可以是任意型别的. 组件有: - 容器: 管理某类对象的集合. 不同的容器有各自的 ...

  9. OI动态规划&&优化 简单学习笔记

    持续更新!! DP的难点主要分为两类,一类以状态设计为难点,一类以转移的优化为难点. DP的类型 序列DP [例题]BZOJ2298 problem a 数位DP 常用来统计或者查找一个区间满足条件的 ...

随机推荐

  1. linux-redhat-git源码安装

    1.查看是否已安装git,如果存在自带的git,则卸载 查看git版本 $ git --version 删除自带git $ yum remove git 2.安装依赖包 $ yum -y instal ...

  2. Judy Array - Example

    “ In computer science and software engineering, a Judy array is a data structure that has high perfo ...

  3. 2. Get the codes from GIT

    Clone the code from git.

  4. htmlparser学习(原创)

    --thumbelina.jar  这是一个演示图片搜索和显示的小程序JFrame Preferences.userNodeForPackage(getClass());  根据传入的class所在包 ...

  5. MySQL的left on 【zt】

    MySQL的left on [zt] (2008-11-03 17:27:30) 转载▼ 标签:  it 分类: 学习笔记 MySQL多表连接查询Left Join,Right Join php开源嘛 ...

  6. Python GUI 编程

    Python GUI编程(Tkinter) Python 提供了多个图形开发界面的库,几个常用 Python GUI 库如下: Tkinter: Tkinter 模块(Tk 接口)是 Python 的 ...

  7. 2018.09.24 codeforces 1053C. Putting Boxes Together(线段树)

    传送门 就是让你维护动态的区间带权中位数. 然而昨晚比赛时并没有调出来. 想找到带权中位数的中点可以二分(也可以直接在线段树上找). 也就是二分出第一个断点,使得断点左边的和恰好大于或等于断点右边的和 ...

  8. 2018.07.08 hdu5316 Magician(线段树)

    Magician Problem Description Fantasy magicians usually gain their ability through one of three usual ...

  9. 右值引用和std::move函数(c++11)

    1.对象移动 1)C++11新标准中的一个最主要的特性就是移动而非拷贝对象的能力 2)优势: 在某些情况下,从旧内存拷贝到新内存是不必要的,此时对对象进行移动而非拷贝可以提升性能 有些类如IO类或un ...

  10. 优秀前端工程师必备: 非常常用的checkbox的骚操作---全选和单选demo

    提要: 前端开发的时候, 经常会遇到表格勾选, 单个勾选判断是否全选的事情.趁着有时间, 总结一下以备不时之需! 就像下面这个栗子: 1 源代码: h5 // 全选框 <input type=& ...