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\) 每个串依次进行匹配 同时记 ...
随机推荐
- js函数和window对象
- 2. HTML常用标签
相信大家常常会打开浏览器搜索一些内容或者浏览一些网站,在浏览器的页面上会呈现很多内容,但是具体的形式无非就是图片.文字以及链接(可以点击进入另一个页面的特殊文字),其中文字承载着巨大的作用,传递着各种 ...
- UEditor显示Invalid or unexpected token
原文链接http://www.qqdeveloper.com/a/53.html 问题背景 数据修改操作,需要做一个数据内容回显,该内容中包含代码.图片.普通文本等等内容,反正就是各种内容. 当 ...
- 理解Linux系统调用
目录 1.什么是系统调用 2.linux的系统调用 3.linux系统调用实现 1.什么是系统调用 系统调用,指的是操作系统提供给用户程序调用的一组特殊接口,用户程序可以根据这组接口获得操作系统内核的 ...
- 反射vs简单工厂模式
interface Computer { void printpc(); } class lenovo implements Computer { @Override public void prin ...
- 16-oauth2-oidc-Client实现
1-新建.net core2.1 mvc网站 2-在Startup.config文件增加相关代码, 下面代码已经配置好oidc客户端了,并设置本mvc启动ip为5009 public void Con ...
- 20145202mc《计算机病毒》实践2
网站检测 http://www.virustotal.com太慢了实在,所以我换成了http://www.virscan.org/ lab01-01.exe 文件行为 lab01-01.dll 可以基 ...
- FpSpread基本句法
1, 在调用的.aspx页面开头注册: "FarPoint.Web.Spread" Assembly="FarPoint.Web.SpreadJ, V ...
- 武汉Uber优步司机奖励政策(8月31日~9月6日)
·奖励前提 *必须满足当周平均评分4.7星及以上,且当周接单率70%及以上,当周在线5小时且完成5单,才有资格获得奖励 * 各组别必须满足当周要求的成单率才有资格获得奖励,成单率由当周 滴滴快车单单2 ...
- Mac下node.js安装与卸载
安装: 访问 http://nodejs.org/ 进入官网,下载 Mac 版本的 node.js,双击打开安装即可. 通过终端输入命令 node -v 验证 node 是否安装正确:npm -v 验 ...