HDU 1403 Longest Common Substring(后缀自动机——附讲解 or 后缀数组)
Description
For example:
str1 = banana
str2 = cianaic
So the Longest Common Substring is "ana", and the length is 3.
Input
Process to the end of file.
Output
如果建立在学过AC自动机的基础上的话,下面的par(parent)指针大概就相当于AC自动机中的失配指针(我不知道叫什么,反正就是匹配失败用的那个……),也就是说一点p的失配指针所指向的点为p的最长后缀,这样就比较好说了。
然后新增一个结点np(w),之前的最后一个点,也就是last指针,必然需要接上w作为下一个点。而last的失配指针指向的点,都是从root走到last的字符串的后缀,都需要接上w。
如果某一点p没有到w的边,那么直接建边就好了。如果能一直走到root都没有w的边,那说明w第一次出现,那么np的失配指针就要指向root。
如果走到一个点p之后,p所指向的w已经有所属了(即!p->go[w]),那么我们不能直接删掉原来的边然后接到np上去(这样会丢失了一条边)。如果p->val + 1 == q->val(val为从root一直走走到某个点的步数,应该是算最长的那条边),那么可以直接让np的失配指针指向p->go[w]。
如果p->val + 1 != q->val,那么我们只能新建一个结点nq为q(p->go[w])的副本(把q的所有边(包括失配边)都复制到nq上),再把q和np的失配指针指向nq。nq->val = p->val + 1,把p的本来指向q的
所有后缀指向nq。
至于为什么要新增一个结点嘛,我想大概是因为如果直接用原来那个结点,会导致val的计算出问题,也就是失配指针所指向的东东不是真正的后缀(可以参考HDU1403看看后缀自动机是怎么用的O__O"…)
每次extend都至多增加两个点,空间复杂度为O(n),时间复杂度不会算,大概也是O(n)
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std; const int MAXN = + ;
char buf[MAXN];
struct State {
State *par, *go[];
int val;/*
State() :
par(0), val(0) {
memset(go, 0, sizeof go);
}*/
}*root, *last;
State statePool[MAXN * ], *cur; void init() {
memset(statePool, , * strlen(buf) * sizeof(State));
cur = statePool;
root = last = cur++;
} void extend(int w) {
State *p = last, *np = cur++;
np->val = p->val + ;
while (p && !p->go[w])
p->go[w] = np, p = p->par;
if (!p) np->par = root;
else {
State*q = p->go[w];
if (p->val + == q->val) np->par = q;
else {
State *nq = cur++;
memcpy(nq->go, q->go, sizeof q->go);
nq->val = p->val + ;
nq->par = q->par;
q->par = nq;
np->par = nq;
while (p && p->go[w] == q)
p->go[w] = nq, p = p->par;
}
}
last = np;
} int main() {
char *pt;
while(scanf("%s", buf) != EOF) {
init();
for (pt = buf; *pt; ++pt)
extend(*pt - 'a');
scanf("%s", buf);
State *t = root;
int l = , ans = ;
for (pt = buf; *pt; ++pt) {
int w = *pt - 'a';
if (t->go[w]) {
t = t->go[w];
++l;
} else {
while (t && !t->go[w]) t = t->par;
if (!t) l = , t = root;
else {
l = t->val + ;
t = t->go[w];
}
}
ans = max(ans, l);
}
printf("%d\n", ans);
}
return ;
}
思路:后缀数组。用一个奇怪的字符连接两个字符串,求排名相邻但原来不在同一个字符串的后缀的最长公共前缀(LCP)。(前缀极大相似的字符串的rank一定是相邻的)
有关后缀数组可以查阅国家集训队论文,2004年许智磊的《后缀数组》和2009年罗穗骞的《后缀数组——处理字符串的有力工具》
#include <cstdio>
#include <cstring>
using namespace std; #define MAXN 200005
char S[MAXN], s2[MAXN];
int n, sa[MAXN], height[MAXN], rank[MAXN], tmp[MAXN], c[MAXN];
int apart;
//rank[i] i的名次
//sa[i] 第i名的位置
//height[i] 第i名和第i-1名的LCP
void makesa(int m) { // O(MAXN * log MAXN)
int i, j, k;
memset(c, , m * sizeof(int));
for(i = ; i < n ; ++i) ++c[rank[i] = S[i]];
for(i = ; i < m; ++i) c[i] += c[i - ];
for(i = ; i < n ; ++i) sa[--c[rank[i]]] = i;
for(k = ; k < n; k <<= ) {
for(i = ; i < n; ++i) {
j = sa[i] - k;
if(j < ) j += n;
tmp[c[rank[j]]++] = j;
}
sa[tmp[c[] = ]] = j = ;
for(i = ; i < n; i++) {
if(rank[tmp[i]] != rank[tmp[i-]] || rank[tmp[i] + k] != rank[tmp[i - ] + k])
c[++j] = i;
sa[tmp[i]] = j;
}
memcpy(rank, sa, n * sizeof(int));
memcpy(sa, tmp, n * sizeof(int));
if(j >= n - ) break;
}
} void calheight() {
int i, j, k = ;
for(i = ; i < n; height[rank[i++]] = k) {
if(k > ) --k;
for(j = sa[rank[i] - ]; S[i + k] == S[j + k]; ++k) ;
}
} int main() {
while(scanf("%s%s", S, s2) != EOF) {
apart = strlen(S);
strcat(S, "*");
strcat(S, s2);
n = strlen(S);
makesa( << );
calheight();
int ans = ;
for (int i = ; i < n; ++i)
if((sa[i - ] < apart && sa[i] > apart) || (sa[i - ] > apart && sa[i] < apart))
if (height[i] > ans) ans = height[i];
printf("%d\n",ans);
}
return ;
}
以下转自:http://hi.baidu.com/myidea/item/142c5cd45901a51820e25039
首先CTSC只有Silver(注意不是Sliver.....被gyz吐槽了......捂),Day2第一题由于一直以来对01串很敏感,所以马上短路,认为是个神级DP题,要用到01串的各种性质,所以80分算法都没有打,不然就Gold了.
补一下后缀自动机,比较难懂,不管是FHQ的还是CLJ的(注:WC2012没去,名额限制啊),
感谢本校的Neroysq神犇的cnblog<后缀自动机初探>举了一个简单而且很有代表性的例子(FHQ的回文串太囧了)
这个例子就是aabbabd,以此构造后缀自动机(请耐心看,因为前面几步没有体现算法)
有几点要记得:
1.由一个接受态沿Parent往前走所到的状态也是接受态
2.一个节点及其父辈的代表的串有相同的后缀
1.首先神马都没有:

此时后缀只有一个就是空串:

红字表示此节点代表最长串的长度,一个节点可能代表多个串
2.现在构建"a"的自动机,就是下面这样

现在后缀变成了这样:

3.然后以上为基础构建"aa"的自动机
现在想一下,由S或者说0号节点可以接受的后缀为空串和"a"这两个,那么现在要将"aa"和"a"这两个后缀更新到后缀自动机中,那么1号节点的后缀"a"就要加入一个字符"a",而空串也要加入字符"a"
也就是所有之前的后缀都要在后面加入一个字符"a".
但是由于1号节点之前所代表的后缀"a"和1的Parent所代表的后缀(空串)+"a"代表的一样,所以,无需更新1及之前的可接受态
如下图:

自动机就变成了如下:

3.更新自动机变成"aab"自动机
同上加所有接受态也要调整,就是在后面加上"b"字符:

这时,由于1,2节点无法代表三个后缀的任意一个,所以除空串的所有后缀都由3代替
这时3号节点和0号节点为接受态.
自动机成了这样:

具体过程是这样的:
S1:新建节点3
S2:找到最后一个后缀也就是最后一个接受态是节点2
S3:2号节点直接连向3,表示插入后缀"aab"
S4:向上找2的Parent,1号节点,向3连边,表示插入后缀"ab"
S5:找到S,连边,表示插入后缀"b".
S6:没有其他接受态了,那么3的上一个接受态为S,Parent[3]=S
4:更新成"aabb"的自动机
同理,在所有接受态后加上字符"b"
不过由于接受态0(S)的转移"b"已经存在,那么,由于不能破坏原来的中间态的转移,只能新建一个节点,来代替接受态0(S)的转移节点

自动机成了这样:

找到0(S)时,发现转移"b"已经有节点占了,所以新建节点5,将3号所有信息Copy(包括Parent),然后更新len值,就是node[5]->len=node[5]->parent->len+1,所以5号节点可以代表后缀空串(0号代表的串)+字符"b"=后缀"b",节点3成了中间态,所以将节点为原接受态的节点指向3的转移改为指向5,这时,我们发现指向3的原接受态节点一定是当前节点0(S)及当前未访问的原接受态节点,所以可以直接沿着Parent往上更新.
然后节点5的Parent及祖先加入了现在的接受态
再次重申一点:一个节点及其父辈的代表的串有相同的后缀,且代表串长度递减,由于5号节点是接受态,所以他的父辈也是接受态,同时反过来也一样,与任意接受态拥有相同后缀的长度小于当前节点的未访问节点一定是当前节点的父辈,如与5号节点有相同后缀的长度小于5号节点的未访问的节点一定是5号的父辈,一定可以作为接受态.
因此为了维护这个性质,我们应该将3号节点的父亲重定义为5
到这里基本上应该明白了
就将剩下的构造过程放出来:


代码什么的如下:
插入一个节点:

意义:
当前新建节点为np,最后的接受态为tail,接受态指针p,pool是内存池,就是一个很大的数组,2*n的空间吧
init就是0号节点
Step 1:建立节点np,p指向tail,np的len更新
Step 2:沿着Parent寻找第一个有转移冲突的接受态节点并沿途更新转移
Step 3:
如果找不到,np的Parent更新为init.
如果正好冲突的节点长度为当前接受态节点那么np的Parent赋为冲突的节点.
否则,新建节点r,Copy冲突节点所有信息,更新r->len=p->len+1,将冲突节点的Parent和np的Parent赋为r,再往前更新与冲突节点有关的转移.
至此结束..........
个人感觉应该表达还算清楚吧(=.=)......
HDU 1403 Longest Common Substring(后缀自动机——附讲解 or 后缀数组)的更多相关文章
- hdu 1403 Longest Common Substring(最长公共子字符串)(后缀数组)
http://acm.hdu.edu.cn/showproblem.php?pid=1403 Longest Common Substring Time Limit: 8000/4000 MS (Ja ...
- HDU - 1403 - Longest Common Substring
先上题目: Longest Common Substring Time Limit: 8000/4000 MS (Java/Others) Memory Limit: 65536/32768 K ...
- HDU 1403 Longest Common Substring(后缀数组,最长公共子串)
hdu题目 poj题目 参考了 罗穗骞的论文<后缀数组——处理字符串的有力工具> 题意:求两个序列的最长公共子串 思路:后缀数组经典题目之一(模版题) //后缀数组sa:将s的n个后缀从小 ...
- hdu 1403 Longest Common Substring 后缀数组 模板题
题目链接 题意 问两个字符串的最长公共子串. 思路 加一个特殊字符然后拼接起来,求得后缀数组与\(height\)数组.扫描一遍即得答案,注意判断起始点是否分别在两个串内. Code #include ...
- HDU 1403 Longest Common Substring(最长公共子串)
http://acm.hdu.edu.cn/showproblem.php?pid=1403 题意:给出两个字符串,求最长公共子串的长度. 思路: 刚开始学后缀数组,确实感觉很难,但是这东西很强大,所 ...
- POJ 2774 Long Long Message&&HDU 1403 Longest Common Substring&&COJ 1203
后缀数组的买1送2题... HDU的那题数据实在是太水了,后来才发现在COJ和POJ上都是WA..原因在一点:在建立sa数组的时候里面的n应该是字符串长度+1....不懂可以去看罗大神的论文... 就 ...
- SPOJ LCS Longest Common Substring 和 LG3804 【模板】后缀自动机
Longest Common Substring 给两个串A和B,求这两个串的最长公共子串. no more than 250000 分析 参照OI wiki. 给定两个字符串 S 和 T ,求出最长 ...
- 【HDOJ】1403 Longest Common Substring
后缀数组2倍增可解. #include <cstdio> #include <cstring> #include <cstdlib> #define MAXM 28 ...
- 【SPOJ】Longest Common Substring II (后缀自动机)
[SPOJ]Longest Common Substring II (后缀自动机) 题面 Vjudge 题意:求若干个串的最长公共子串 题解 对于某一个串构建\(SAM\) 每个串依次进行匹配 同时记 ...
随机推荐
- Jquery与js简单对比
//Javascript window.onload=function () { var oBtn=document.getElementById('btn1'); oBtn.onclick=func ...
- “==”与equals的区别
“==”与equals的区别: “==”:两个对象比较的是对象的引用地址比较,对象的hashCode值是对象的引用地址,只有两个对象的hashCode值一样,此比较符才会返回true,否则即使两个对象 ...
- CentOS 7.x下升级Python版本到3.x系列(新老版本共存)
由于python官方已宣布2.x系列即将停止支持,为了向前看,我们升级系统的python版本为3.x系列服务器系统为当前最新的CentOS 7.4 1.安装前查看当前系统下的python版本号 # p ...
- hadoop生态搭建(3节点)-08.kafka配置
如果之前没有安装jdk和zookeeper,安装了的请直接跳过 # https://www.oracle.com/technetwork/java/javase/downloads/java-arch ...
- 20190118-利用Python实现Pig Latin游戏
1.利用Python实现Pig Latin字母游戏 “Pig Latin”是一个英语儿童文字改写游戏,整个游戏遵从下述规则:a. 元音字母是‘a’.‘e’.‘i’.‘o’.‘u’.字母‘y’在不是第一 ...
- python3 练习题100例 (一)
断断续续的学了很久的python,有很多又忘记了.从今天开始用实例再进行一次学习,并记录.本人小白一个,请大家多多指教. #!/usr/bin/env python3 # -*- coding: ut ...
- ubuntu 杂记
修改/home下中文目录 网易云sudo解决 https://jingyan.baidu.com/article/1e5468f956a15c484861b770.html 字体 https: ...
- C#/STM32 WAV转byte WAV数据格式
最近在做STM32音乐播放器,选取了最容易做的WAV格式. 为了更方便开发自己做了一个WAV转Byte的小上位机 附软件下载链接 链接:https://pan.baidu.com/s/1Zz7bczZ ...
- 小程序开发-13-小程序wxs的应用
内容简介的换行 问题:因为微信的<text></text>标签能够转义\n,所以从服务器加载来的数据我们可以直接放到这个标签中,\n就会自己换行了.问题是服务器返回来的数据多了 ...
- C# 面试题 (二)
1. 什么是C#? C#是微软公司发布的一种面向对象的.运行于.NET Framework之上的高级程序设计语言.C#是一种安全的.稳定的.简单的.优雅的,由C和C++衍生出来的面向对象的编程语言. ...