SPOJ 1811 Longest Common Substring(求两个串的最长公共子串 || 或者n个串)
http://www.spoj.com/problems/LCS/
题目:求两个串的最长公共子串
参考:https://www.cnblogs.com/autoint/p/10345276.html:
分析:
给定两个字符串 S 和 T ,求出最长公共子串,公共子串定义为在 S 和 T 中 都作为子串出现过的字符串 X 。
我们为字符串 S 构造后缀自动机。
我们现在处理字符串 T ,对于每一个前缀都在 S 中寻找这个前缀的最长后缀。换句话 说,对于每个字符串 T 中的位置,我们想要找到这个位置结束的 S 和 T 的最长公 共子串的长度。
为了达到这一目的,我们使用两个变量,当前状态 v 和 当前长度 l 。这两 个变量描述当前匹配的部分:它的长度和它们对应的状态。
一开始 v=t_0且 l=0 ,即,匹配为空串。
现在我们来描述如何添加一个字符 T[i] 并为其重新计算答案:
如果存在一个从 v 到字符 T[i] 的转移,我们只需要转移并让 l 自增一。
如果不存在这样的转移,我们需要缩短当前匹配的部分,这意味着我们需要按照以下后 缀链接进行转移:
v=link(v)
与此同时,需要缩短当前长度。显然我们需要将 l 赋值为 len(v) ,因为经过这个后缀链接后我们到达的状态所对应的最长字符串是一个子串。
如果仍然没有使用这一字符的转移,我们继续重复经过后缀链接并减小 l ,直到我们 找到一个转移或到达虚拟状态 -1 (这意味着字符 T[i] 根本没有在 S 中出现过, 所以我们设置 v=l=0 )。
问题的答案就是所有 l 的最大值。
这一部分的时间复杂度为 O(length(T)) ,因为每次移动我们要么可以使 l 增加一, 要么可以在后缀链接间移动几次,每次都减小 l 的值。
时间复杂度O(|S|+|T|)
#include <bits/stdc++.h>
#define LL long long
#define P pair<int, int>
#define lowbit(x) (x & -x)
#define mem(a, b) memset(a, b, sizeof(a))
#define rep(i, a, n) for (int i = a; i <= n; ++i)
const int maxn =;
#define mid ((l + r) >> 1)
#define lc rt<<1
#define rc rt<<1|1
using namespace std;
string str1,str2;
struct SAM{ int trans[maxn<<][], slink[maxn<<], maxlen[maxn<<];
int last, now, root, len;
inline void newnode (int v) {
maxlen[++now] = v;
} inline void extend(int c) {
newnode(maxlen[last] + );
int p = last, np = now;
// 更新trans
while (p && !trans[p][c]) {
trans[p][c] = np;
p = slink[p];
}
if (!p) slink[np] = root;
else {
int q = trans[p][c];
if (maxlen[p] + != maxlen[q]) {
// 将q点拆出nq,使得maxlen[p] + 1 == maxlen[q]
newnode(maxlen[p] + );
int nq = now;
memcpy(trans[nq], trans[q], sizeof(trans[q]));
slink[nq] = slink[q];
slink[q] = slink[np] = nq;
while (p!=- && trans[p][c] == q) {
trans[p][c] = nq;
p = slink[p];
}
}else slink[np] = q;
}
last = np;
// 初始状态为可接受状态 }
inline void init()
{
memset(trans,,sizeof(trans));
memset(slink,,sizeof(slink));
memset(maxlen,,sizeof(maxlen));
root = last=now=;
}
inline void build(string s)
{
len=s.size();
for(int i= ; i<len ; i++)
extend(s[i]-'a');
}
inline int work(string s)
{
int Len=s.size();
int t1=;
int ret=;
int now=root;
for(int i= ; i<Len ; i++)
{
int ind=s[i]-'a';
while(now!= && trans[now][ind]==)
{
now=slink[now];
if(now!=) t1=maxlen[now];
}
if(now==)
{
now=root ; t1=;
}
else
{
now=trans[now][ind];
t1++;
ret=max(ret,t1);
}
}
return ret;
} }sam; int main()
{
sam.init();
cin>>str1>>str2;
sam.build(str1);
printf("%d\n",sam.work(str2));
}
时间复杂的O(n)
求n个串的最长公共字串
本题容易看出就是将所有匹配长度记录在状态上然后取min后再对所有状态取max。
但是不要忘记了一点:更新parent树的祖先。
为什么呢?首先如果子树被匹配过了,那么长度一定大于任意祖先匹配的长度(甚至有些祖先匹配长度为0!为什么呢,因为我们在匹配的过程中,只是找到一个子串,可能还遗漏了祖先没有匹配到,这样导致了祖先的记录值为0,那么在对对应状态取min的时候会取到0,这样就wa了。而且注意,如果匹配到了当前节点,那么祖先们一定都可以赋值为祖先的length!因为当前节点的length大于任意祖先。(
比如数据
acbbc
bc
ac
答案应该是1没错吧。如果没有更新祖先,那么答案会成0。
这个多想想就行了。
所以以后记住:对任意多串匹配时,凡是对同一个状态取值时,要注意当前状态的子树是否比当前状态记录的值优。
#include <bits/stdc++.h>
#define LL long long
#define P pair<int, int>
#define lowbit(x) (x & -x)
#define mem(a, b) memset(a, b, sizeof(a))
#define rep(i, a, n) for (int i = a; i <= n; ++i)
const int maxn = ;
#define mid ((l + r) >> 1)
#define lc rt<<1
#define rc rt<<1|1
using namespace std;
// __int128 read() { __int128 x = 0, f = 1; char c = getchar(); while (c < '0' || c > '9') { if (c == '-') f = -1; c = getchar(); } while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); } return x * f;}
// void print(__int128 x) { if (x < 0) { putchar('-'); x = -x; } if (x > 9) print(x / 10); putchar(x % 10 + '0');}
const LL mod = 1e9 + ;
int len;
struct SAM{ int trans[maxn<<][], slink[maxn<<], maxlen[maxn<<];
// 用来求endpos
int indegree[maxn<<], endpos[maxn<<], rank[maxn<<], ans[maxn<<];
// 计算所有子串的和(0-9表示)
LL sum[maxn<<];
int mx[maxn],mn[maxn];
int last, now, root; inline void newnode (int v) {
maxlen[++now] = v;
mn[now]=v;
mem(trans[now],);
} inline void extend(int c) {
newnode(maxlen[last] + );
int p = last, np = now; // 更新trans
while (p && !trans[p][c]) {
trans[p][c] = np;
p = slink[p];
}
if (!p) slink[np] = root;
else {
int q = trans[p][c];
if (maxlen[p] + != maxlen[q]) {
// 将q点拆出nq,使得maxlen[p] + 1 == maxlen[q]
newnode(maxlen[p] + );
int nq = now; memcpy(trans[nq], trans[q], sizeof(trans[q]));
slink[nq] = slink[q];
slink[q] = slink[np] = nq;
while (p && trans[p][c] == q) {
trans[p][c] = nq;
p = slink[p];
}
}else slink[np] = q;
}
last = np;
// 初始状态为可接受状态
endpos[np] = ;
} inline void init()
{
root = last = now = ;
slink[root]=;
mem(trans[root],);
mem(mx,);
} inline void getEndpos() {
// topsort
for (int i = ; i <= now; ++i) indegree[ maxlen[i] ]++; // 统计相同度数的节点的个数
for (int i = ; i <= now; ++i) indegree[i] += indegree[i-]; // 统计度数小于等于 i 的节点的总数
for (int i = ; i <= now; ++i) rank[ indegree[ maxlen[i] ]-- ] = i; // 为每个节点编号,节点度数越大编号越靠后 }
inline void woke(char *s)
{
int x=root;
int t1=;
int len=strlen(s);
for(int i= ; i<len ; i++)
{
int ind=s[i]-'a';
if(trans[x][ind])
{
x=trans[x][ind];
t1++;
mx[x]=max(mx[x] ,t1);
}
else
{
while(x!= && trans[x][ind]==)
{
x=slink[x];
}
if(x==)
{
x=root;
t1=; }
else
{
t1=maxlen[x]+;
x=trans[x][ind];
mx[x]=max(mx[x],t1);
}
}
}
for(int i=now ; i>= ; i--)
{
int x=rank[i];
mn[x] = min(mn[x] , mx[x]);
if(slink[x]) mx[slink[x]] = max(mx[slink[x]] , mx[x]);
mx[x]=;
}
} }sam;
char s[maxn];
int main()
{ string T;cin>>T;
sam.init();
len=T.size();
for(int i= ; i<len ; i++)
sam.extend(T[i]-'a');
sam.getEndpos();
while(~scanf("%s",s))
{
sam.woke(s);
}
int ans=;
// cout<<sam.now<<endl;
for(int i= ; i<=sam.now ; i++)
{
ans=max(ans,sam.mn[i]);
}
printf("%d\n",ans); //- sam.all();
}
SPOJ 1811 Longest Common Substring(求两个串的最长公共子串 || 或者n个串)的更多相关文章
- SPOJ 1811 Longest Common Substring 后缀自动机
模板来源:http://www.neroysq.com/?p=76 思路:http://blog.sina.com.cn/s/blog_7812e98601012dfv.html 题意就是求两个字符串 ...
- 求两个字符串的最长公共子串——Java实现
要求:求两个字符串的最长公共子串,如“abcdefg”和“adefgwgeweg”的最长公共子串为“defg”(子串必须是连续的) public class Main03{ // 求解两个字符号的最长 ...
- SPOJ 1811. Longest Common Substring (LCS,两个字符串的最长公共子串, 后缀自动机SAM)
1811. Longest Common Substring Problem code: LCS A string is finite sequence of characters over a no ...
- ●SPOJ 1811 Longest Common Substring
题链: http://poj.org/problem?id=2774 题解: 求两个字符串(S,T)的最长公共子串.对 S串建后缀自动机.接下来就用这个自动机去求出能和 S串匹配的 T的每一个前缀的最 ...
- SPOJ 1811 Longest Common Substring (后缀自动机第一题,求两个串的最长公共子串)
题目大意: 给出两个长度小于等于25W的字符串,求它们的最长公共子串. 题目链接:http://www.spoj.com/problems/LCS/ 算法讨论: 二分+哈希, 后缀数组, 后缀自动机. ...
- SPOJ 1811 Longest Common Substring
Description 给出两个字符串,求最长公共子串. Sol SAM. 这题随便做啊...后缀数组/Hash+二分都可以. SAM就是模板啊...直接在SAM上跑就行,没有了 \(go[w]\) ...
- [URAL-1517][求两个字符串的最长公共子串]
Freedom of Choice URAL - 1517 Background Before Albanian people could bear with the freedom of speec ...
- 【java】求两个字符串的最长公共子串
这个是华为OJ上的一道题目.首先,如果我们用java写代码,华为OJ有以下三条规则需遵守,否则编译无法通过或者用例无法通过,规则如下: (1)一定不可以有包名: (2)主类名只能为Main: (3)不 ...
- 求两个字符串的最长公共子串(LCS)
http://tianyunpu2008.blog.163.com/blog/static/6559379920089162236915/
随机推荐
- Python 序列与映射的解包操作-乾颐堂
解包就是把序列或映射中每个元素单独提取出来,序列解包的一种简单用法就是把首个或前几个元素与后面几个元素分别提取出来,例如: first, seconde, *rest = sequence 如果seq ...
- OracleBulkCopy 修正帮
using System;using System.Collections.Generic;using System.Data;using System.Linq;using System.Refle ...
- 【转】jvm 堆内存 栈内存 大小设置
原文地址:http://blog.csdn.net/qh_java/article/details/46608395 4种方式配置不同作用域的jvm的堆栈内存! 1.Eclise 中设置jvm内存: ...
- Android canvas bug
安卓4.1.1-4.1.2的webkit在渲染canvas元素时有bug. 具体表现是出现重影,即canvas的clearRect()方法不能彻底清空画布,仍然保留之前某个状态当“背景”. 目前的修复 ...
- JAVA中简单的MD5加密类(MD5Utils)
MD5加密分析: JDK API: 获取对象的API: 加密的API: package cn.utils; import java.security.MessageDigest; im ...
- static在C和C++里各代表什么含义
转自:http://blog.csdn.net/wanglongfei_hust/article/details/10011503 static关键字有三种使用方式,其中前两种只指在C语言中使用,第三 ...
- Creating Custom UITableViewCells with NIB files
Maksim Pecherskiy 13 November 2012 Well this sucks. Apparently these days you can only use the Inter ...
- .NET基础 (07)异常的处理
异常的处理1 如何针对不同的异常进行捕捉2 如何使用Conditional特性3 如何避免类型转换时的异常 异常的处理 1 如何针对不同的异常进行捕捉 C#中一个try块可以有多个catch块,每个c ...
- 20160214 2016-2017-2 实验二《Java面向对象》实验报告
实验二 面向对象程序设计 (一)单元测试 写一段关于分数标准的代码,如下: public class MyUtil{ public static String percentage2fivegrade ...
- modelsim使用常见问题及解决办法集锦 ②
二.Error deleting “msim_transcript” Error deleting “msim_transcript”:permission denied. Check the Nat ...