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\) 每个串依次进行匹配 同时记 ...
随机推荐
- 【Django笔记四】Django2.0中的表单
一.环境版本信息: 操作系统:windows10 Django版本:2.0.5 Python版本:3.6.4 Mysql版本: 5.5.53 安装mysql 二.基础信息 1.App中的模型mod ...
- LeetCode 简单 -旋转字符串(796)
给定两个字符串, A 和 B. A 的旋转操作就是将 A 最左边的字符移动到最右边. 例如, 若 A = 'abcde',在移动一次之后结果就是'bcdea' .如果在若干次旋转操作之后,A 能变成B ...
- ubuntu—终端安装mysql
---恢复内容开始--- Step 1 : 安装指令 ~$ sudo apt-get install mysql-server Step 2: 查看是否正常安装 ~$ ps aux | grep my ...
- 浅谈vue,小程序,react基础绑定值
最近一直在用react开发项目,碰见的问题千千万,很多,但是都殊途同源,唯一区别大的就是没有像vue的双向绑定,也没有小程序的单向方便,比如: vue v-modal="msg" ...
- js中回调函数写法
第一种方式 function studyEnglish(who){ document.write(who+"学习英语</br>"); } function study( ...
- js之广告
涉及到offset等有关获取各种尺寸问题下 <!doctype html> <html lang="en"> <head> <meta c ...
- 【reidis中ruby模块版本老旧利用rvm来更新】
//gem install redis时会遇到如下的error: //借助rvm来update ruby版本
- PHP二维码生成
原文链接:http://www.qqdeveloper.com/detail/14/1.html 代码下载地址:链接:http://pan.baidu.com/s/1dFgqiaP 密码:lex5 材 ...
- Hbase 表的Rowkey设计避免数据热点
一.案例分析 常见避免数据热点问题的处理方式有:加盐.哈希.反转等方法结合预分区使用. 由于目前原数据第一字段为时间戳形式,第二字段为电话号码,直接存储容易引起热点问题,通过加随机列.组合时间戳.字段 ...
- Java开发小技巧(五):HttpClient工具类
前言 大多数Java应用程序都会通过HTTP协议来调用接口访问各种网络资源,JDK也提供了相应的HTTP工具包,但是使用起来不够方便灵活,所以我们可以利用Apache的HttpClient来封装一个具 ...