hihocoder 后缀自动机专题
一、后缀自动机基本概念的理解
1、首先后缀自动机的状态是由子串的endpos来决定的
子串的endpos是指一个子串可以在原字符串的哪些位置进行匹配,
endpos构成的不同集合划分成不同的状态
关于endpos的性质: s1是s2的子串当且仅当endpos(s1)属于endpos(s2),s1不是s2的子串当前仅当endpos(s1)和endpos(s2)的交集为空
2、对于一个用endpos划分的状态,最长的子串为longest(st),最短的为shortest(st),对于任何包含于该状态的子串,都是longest(st)的后缀;同样,对于一个状态中的longest(st)的后缀,如果后缀的长度在longest和shortest之间,那么它就属于这个状态。
如此可以这样理解,一个endpos划分的状态,实际上是longest形成的一系列后缀
3、Link
link是将不同endpos间连接起来的边,实际上是把系列的中断相连
4、Transition function
对于一个状态,首先找到它下一个可能出现的字符有哪些,实际上就是只需要把longest后面添加一下新的字符,然后看这个新的串被哪个状态所包含
那么它的那一系列后缀也被这个状态所包含。
暴力做法 (关于endpos)
hihocoder 1441
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#define fi first
#define se second
using namespace std;
typedef pair<int, int> PII;
char str[], temp[];
vector<int> endpos[][];
int n; bool ok(int x, int y, int t){
int len = y-x+;
int s = t-len+;
if(s < ) return false;
for(int i = ; i < len; i++)
if(str[i+x] != str[s+i]) return false;
return true;
}
bool cmp(int x, int y, char* temp){
int len = y-x+;
if(len != strlen(temp)) return false;
for(int i = ; i < len; i++) if(str[i+x] != temp[i]) return false;
return true;
}
void print(char* str, int x, int y){
for(int i = x; i <= y; i++) cout<<str[i];
} int main(){
cin>>str;
int len = strlen(str);
for(int i = ; i < len; i++){
for(int j = i; j < len; j++)
for(int k = ; k < len; k++)
if(ok(i, j, k)) endpos[i][j].push_back(k+);
}
cin>>n;
while(n--){
cin>>temp;
PII s;
for(int i = ; i < len; i++)
for(int j = i; j < len; j++)
if(cmp(i, j, temp))
{ s = {i, j}; break; }
auto x = endpos[s.fi][s.se];
int longest = , shortest = 1e9;
PII ll, ss;
for(int i = ; i < len; i++)
for(int j = i; j < len; j++){
auto y = endpos[i][j];
if(x.size() != y.size()) continue;
int fail = ;
for(int k = ; k < x.size(); k++) if(x[k] != y[k]) fail = ;
if(fail) continue;
if(longest < j-i+) { longest = j-i+; ll = {i, j}; }
if(shortest > j-i+) { shortest = j-i+; ss = {i, j}; }
}
print(str, ss.fi, ss.se); cout<<" ";
print(str, ll.fi, ll.se); cout<<" ";
for(auto tt : x) cout<<tt<<" "; cout<<endl;
}
}
二、算法部分
hihocoder上讲的很详细
但是只是给出了实现的做法,算法的正确性并没有给出详尽的证明,以后看情况补充吧(挖坑)
算法分成三种情况。运用增量法,取上一次的状态
顺着它的link走,可以得到它的所有后缀,所以就是所有后缀加上这次新的字符
首先建立一个新的状态z代表S[1...i+1],maxlen显然是i+1
①如果link-path上都没有这个新的字符,就全部直接连新的状态,link[z] = s,更新minlen
②如果link-path上有一个状态x,它加上新的字符可以转移到另一个状态y,做如下处理
1、如果maxlen[x]+1 = maxlen[y],那么说明实际上x是z的longest的一系列后缀,只不过不在同一状态中,所以直接link[z] = x即可,更新minlen
2、如果maxlen[x]+1 < maxlen[y],那么我们就把y结点分成两部分,一部分p是maxlen[y] <= maxlen[x]+1,这部分实际上和1是一样的。另一部分q是maxlen[y] > maxlen[x] + 1
实际上x并不能转移到q,所以q留在原地,新建一个结点代表p,让x连向p,然后link[p] = x, link[q] = link[z] = p。
对于剩下的link-path上的状态,如果它们连向y的话,就重新连向p。最后更新一下p的minlen
三、题目练习
hihocoder 1445
题目大意:给出一个串,求出不重复子串的个数
答案就是每个状态的longest减去shortest,可以保证没有重复的情况出现
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
int n = , len, st;
const int maxL = 1e6 + ;
int maxlen[*maxL], minlen[*maxL], trans[*maxL][], slink[*maxL];
int new_state(int _maxlen, int _minlen, int *_trans, int _slink){
maxlen[n] = _maxlen;
minlen[n] = _minlen;
for(int i = ; i < ; i++){
if(_trans == NULL)
trans[n][i] = -;
else
trans[n][i] = _trans[i];
}
slink[n] = _slink;
return n++;
} int add_char(char ch, int u){
int c = ch - 'a';
int z = new_state(maxlen[u]+, -, NULL, -);
int v = u;
while(v != - && trans[v][c] == -){
trans[v][c] = z;
v = slink[v];
}
if(v == -){
minlen[z] = ;
slink[z] = ;
return z;
}
int x = trans[v][c];
if(maxlen[v] + == maxlen[x]){
minlen[z] = maxlen[x] + ;
slink[z] = x;
return z;
}
int y = new_state(maxlen[v] + , -, trans[x], slink[x]);
slink[y] = slink[x];
minlen[x] = maxlen[y] + ;
slink[x] = y;
minlen[z] = maxlen[y] + ;
slink[z] = y;
int w = v;
while(w != - && trans[w][c] == x){
trans[w][c] = y;
w = slink[w];
}
minlen[y] = maxlen[slink[y]] + ;
return z;
} char str[maxL];
int main()
{
cin>>str;
st = new_state(, , NULL, -);
int len = strlen(str);
for(int i = ; i < len; i++) {
st = add_char(str[i], st);
}
long long ans = ;
for(int i = ; i < n; i++) ans += (maxlen[i] - minlen[i] + );
cout<<ans<<endl;
return ;
}
hihocoder 1449
给定一个串,要求求出长度为k的子串中重复最多的串出现的次数
问题实际上转换成了求endpos的大小
在建立完后缀自动机后,我们用link可以连接成一棵树
对于父结点的孩子若干个孩子,实际上我们有
endpos[fa] >= sigma(endpos[son])
一般情况下是等于的,但是如果这一点的状态恰好表示了一个前缀,那么就要加1
而前缀的那些点其实是加入的那些,所以加入的过程中标记一下即可
最后求答案的时候,对于一个状态我们实际上要用endpos[x]更新minlen[x] ~ maxlen[x]
但是实际上我们只需要更新maxlen,原因是答案一定是随长度递增的
所以最后做一个这样的处理 ans[i] = max(ans[i], ans[i+1]就可以了
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
using namespace std;
int n = , len, st;
const int maxL = 1e6 + ;
int maxlen[*maxL], minlen[*maxL], trans[*maxL][], slink[*maxL], lab[*maxL], ans[*maxL], son[*maxL], endpos[*maxL];
int new_state(int _maxlen, int _minlen, int *_trans, int _slink){
maxlen[n] = _maxlen;
minlen[n] = _minlen;
for(int i = ; i < ; i++){
if(_trans == NULL)
trans[n][i] = -;
else
trans[n][i] = _trans[i];
}
slink[n] = _slink;
return n++;
} int add_char(char ch, int u){
int c = ch - 'a';
int z = new_state(maxlen[u]+, -, NULL, -); lab[z] = ;
int v = u;
while(v != - && trans[v][c] == -){
trans[v][c] = z;
v = slink[v];
}
if(v == -){
minlen[z] = ;
slink[z] = ;
return z;
}
int x = trans[v][c];
if(maxlen[v] + == maxlen[x]){
minlen[z] = maxlen[x] + ;
slink[z] = x;
return z;
}
int y = new_state(maxlen[v] + , -, trans[x], slink[x]);
slink[y] = slink[x];
minlen[x] = maxlen[y] + ;
slink[x] = y;
minlen[z] = maxlen[y] + ;
slink[z] = y;
int w = v;
while(w != - && trans[w][c] == x){
trans[w][c] = y;
w = slink[w];
}
minlen[y] = maxlen[slink[y]] + ;
return z;
} char str[maxL];
int main()
{
cin>>str;
st = new_state(, , NULL, -);
int len = strlen(str);
for(int i = ; i < len; i++) {
st = add_char(str[i], st);
}
for(int i = ; i <= n; i++) son[slink[i]]++;
queue<int> Q;
for(int i = ; i <= n; i++) if(son[i] == ) Q.push(i), endpos[i] = ;
while(!Q.empty()){
int x = Q.front(); Q.pop();
if(x == ) continue;
int y = slink[x];
son[y]--; endpos[y] += endpos[x];
if(son[y] == ){
if(lab[y]) endpos[y]++;
Q.push(y);
}
}
for(int i = ; i <= n; i++) ans[maxlen[i]] = max(ans[maxlen[i]], endpos[i]);
for(int i = len-; i >= ; i--) ans[i] = max(ans[i], ans[i+]);
for(int i = ; i <= len; i++) cout<<ans[i]<<endl;
return ;
}
hihocoder 后缀自动机专题的更多相关文章
- 后缀自动机专题(hihocoder)
传送门 #1445 : 后缀自动机二·重复旋律5 题意: 给出字符串\(s\),询问字符串\(s\)中有多少不同的子串. 思路: 考虑对\(s\)建后缀自动机,那么\(\sum (len[i]-len ...
- hihoCoder 后缀自动机三·重复旋律6
后缀自动机三·重复旋律6 时间限制:15000ms 单点时限:3000ms 内存限制:512MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为一段数构成的数列. 现在小Hi ...
- hihocoder 后缀自动机五·重复旋律8 求循环同构串出现的次数
描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一段音乐旋律可以被表示为一段数构成的数列. 小Hi发现旋律可以循环,每次把一段旋律里面最前面一个音换到最后面就成为了原旋律的“循环相似旋律”,还可以 ...
- hihocoder 后缀自动机二·重复旋律5
求不同子串个数 裸的后缀自动机 #include<cstring> #include<cmath> #include<iostream> #include<a ...
- hihocoder 后缀自动机四·重复旋律6
题目 对于\(k\in[1,n]\)求出长度为\(k\)的子串出现次数最多的出现了多少次 我直到现在才理解后缀自动机上的子树和是什么意思 非常显然的一点是 \[endpos(link(u))⊇endp ...
- hihocoder 后缀自动机四·重复旋律7
题目 在\(DAG\)上跑一个\(dp\)就好了 设\(ans_i\)表示到了\(SAM\)的\(i\)位置上所有的子串形成的数的和,之后我们顺便记录一个方案数\(d_i\) 之后我们直接转移就好了 ...
- 【hihocoder#1413】Rikka with String 后缀自动机 + 差分
搞了一上午+接近一下午这个题,然后被屠了个稀烂,默默仰慕一晚上学会SAM的以及半天4道SAM的hxy大爷. 题目链接:http://hihocoder.com/problemset/problem/1 ...
- 【hihoCoder 1466】后缀自动机六·重复旋律9
http://hihocoder.com/problemset/problem/1466 建出A串和B串的两个后缀自动机 对后缀自动机的每个状态求出sg值. 求出B串的\(sum(x)\),表示B有多 ...
- hihoCoder #1465 : 后缀自动机五·重复旋律8
http://hihocoder.com/problemset/problem/1465 求S的循环同构串在T中的出现次数 将串S变成SS 枚举SS的每个位置i,求出以i结尾的SS的子串 与 T的最长 ...
随机推荐
- Apache和Nignx基于三种方式搭建web站点并设置用户访问控制达到优化整个站点性能
个人用户主页: 1:Vim /etc/http/con.d/userdir: UserDir disabled //个人用户主页开启 UserDir public_html //指定 ...
- dedecms左侧导航栏不显示问题
dedecms左侧导航栏不显示问题 在做织梦项目时,经常会碰到后台左侧导航栏不显示的问题,如下所示: 这主要是由于文件权限不足造成的.有两种方法 第一种:把 /data 文件夹全部改成 777 权 ...
- .Net Core On Liunx 环境搭建之 Docker 容器和Nginx
上一篇文章安装了Mysql8数据库,接下开始安装Docker和Nginx 我的思路是这样的,用Docker当运行环境的虚拟机,Nginx当Http服务器用来做反向代理. 服务器环境:阿里云服务器,操作 ...
- Java源码解析——集合框架(三)——Vector
Vector源码解析 首先说一下Vector和ArrayList的区别: (1) Vector的所有方法都是有synchronized关键字的,即每一个方法都是同步的,所以在使用起来效率会非常低,但是 ...
- PHP 面向对象编程笔记 (麦子 php 第二阶段)
类是把具有相似特性的对象归纳到一个类中,类就是一组相同属性和行为的对象的集合.类和对象的关系:类是相似对象的描述,先有类,再有对象.类是对象的抽象,对象是类的实例.通过class关键字创建类,成员属性 ...
- web学习第一天
学习web心得 表格 table,表单 form,跑马灯效果 marquee,导入背景图片<img src="图片路径"> 第一天学的不是很难,作业也相对来说比较简单, ...
- UVA10474 Where is the Marble?【排序】
参考:https://blog.csdn.net/q547550831/article/details/51326321 #include <iostream> #include < ...
- AES128加密-S盒和逆S盒构造推导及代码实现
文档引用了<密码编码学与网络安全--原理和实践>里边的推导过程,如有不妥,请与我联系修改. 文档<FIPS 197>高级加密标准AES,里边有个S盒构造,涉及到了数论和有限域的 ...
- PHP HashTable总结
本篇文章主要是对 PHP HashTable 总结,下面的参考链接是很好的学习资料. 总结 HashTable 又叫做散列表,是一种用于以常数平均时间执行插入.删除和查找的技术.不能有效的支持元素之间 ...
- Hadoop启动后无法启动NodeManager
在配置完Hadoop集群后,使用命令:“start-all.sh”进行启动集群.然后使用命令:“jps”查看进程启动情况,发现没有NodeManager 只需要使用命令:cd /usr/local/ ...