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\) 每个串依次进行匹配 同时记 ...
随机推荐
- 前端ajax的各种与后端交互的姿势
前端中常常用的与后端交换数据的话,通常是要用到ajax这种方法的 但是交互的方式有很多种,很多取决于你后端的属性,我这儿主要列举我目前项目比较常用的两种方式 --一个是我们通用的web api和控制器 ...
- java servlet数据库查询并将数据显示到jsp页面
需要的jar包:mysql-connector-java.jar build path只是个jar包的引用,部署的时候想不丢包最好还是手动拷贝到对应项目的lib文件下. 在try{}中定义的变量为局部 ...
- 获取地图的信息到input里
在最近项目中,我接触了百度地图的API写法,对其中的代码有了一点兴趣,所以我在完成任务后,在办公室里学习了百度地图的相关引用,并申请了服务秘钥: E7PCho0sv3FdzmjC901ttP0HrS9 ...
- Java并发编程:浅析几种线程安全模型 [转]
多线程编程一直是老生常谈的问题,在Java中,随着JDK的逐渐发展,JDK提供给我们的并发模型也越来越多,本文摘取三例使用不同原理的模型,分析其大致原理.目录如下: 1.COW之CopyOnWrite ...
- iOS 11.2 - 11.3.1 越狱教程
iOS 11.2 - 11.3.1 越狱教程 一.准备相应的工具 (1) 下载 CydiaImpactor,官网地址是 http://cydiaImpactor.com (2) 下载 Electra, ...
- 使用Hbuilder开发IOS应用上架审核提示请指定用户在位置许可模式警报中使用位置的预定用途。
使用Hbuilder开发IOS应用时,遇到上架App被拒的问题,被拒原因: 你的应用程序使用位置服务,但并没有按照iOS人机界面指南中的要求,在位置模式警报中阐明它的用途. 要解决此问题,请指定用户在 ...
- PHP基础3--文件加载-错误处理
主要: 1-文件加载 2-错误处理 文件加载 文件加载语句 1) 4个文件加载语句:include, require, include_once, require_once 2) 使用形式 ...
- spark ---词频统计(二)
利用python来操作spark的词频统计,现将过程分享如下: 1.新建项目:(这里是在已有的项目中创建的,可单独创建wordcount项目) ①新建txt文件: wordcount.txt (文件内 ...
- N对数的排列问题 HDU - 2554
N对数的排列问题 HDU - 2554 有N对双胞胎,他们的年龄分别是1,2,3,……,N岁,他们手拉手排成一队到野外去玩,要经过一根独木桥,为了安全起见,要求年龄大的和年龄小的排在一起,好让年龄大的 ...
- Maximum sum
描述 Given a set of n integers: A={a1, a2,-, an}, we define a function d(A) as below: t1 t2 d(A) = max ...