\(\color{purple}{Link}\)

\(\text{Solution:}\)

题目要求找到两个串的最长公共子串。\(LCP\)

我们将两个串中间和末尾插入终止符,并弄到一棵后缀树上去。

然后我们发现,对于一个叶子节点,它属于哪个子串,我们只需要找到它的父边上第一个出现的终止符属于哪个边即可。

这里,我们可以用个奇技淫巧——前缀和实现。

介于\(\text{Suffix Tree}\)的边都是压缩的,所以维护信息变得不是很容易,所以可以采用一个在插入外面进行预处理前缀和的方式维护。

然后,我们只需要找到一个最深的非叶子节点,使得它的子树中既含有第一个串串的终止符,也含有第二个串串的终止符即可。

此时它的答案就是这个点的深度。

\(\text{Suffix Tree}\)的主要问题就在于边上信息的维护。如果找不到一个好的方法去维护,\(\text{Suffix Tree}\)还是很麻烦的。

最近做题,题解区域都没有\(\text{Suffix Tree}\)的题解,做起来真的挺累……我太菜了。

#include<bits/stdc++.h>
#include<ctime>
using namespace std;
const int MAXN=1.2e6+10;
string Z,z;
int n,val[MAXN],ans,tot;
int sum[MAXN],sum2[MAXN];
const int inf=(1<<30);
struct SuffixTree {
int link[MAXN],ch[MAXN][28],now,rem,n;
int start[MAXN],len[MAXN],tail,s[MAXN];
SuffixTree() {
tail=now=1;
rem=n=0;
len[0]=inf;
}
inline int build(int a,int b) {
link[++tail]=1;
start[tail]=a;
len[tail]=b;
return tail;
}
void Extend(int x) {
s[++n]=x;
++rem;
for(int last=1; rem;) {
while(rem>len[ch[now][s[n-rem+1]]])
rem-=len[now=ch[now][s[n-rem+1]]];
int &v=ch[now][s[n-rem+1]];
int c=s[start[v]+rem-1];
if(!v||x==c) {
link[last]=now;
last=now;
if(!v)v=build(n,inf);
else break;
} else {
int u=build(start[v],rem-1);
ch[u][c]=v;
ch[u][x]=build(n,inf);
start[v]+=rem-1;
len[v]-=rem-1;
link[last]=v=u;
last=u;
}
if(now==1)--rem;
else now=link[now];
}
}
} T;
void predfs(int u,int dep) {
if(dep>=inf) {
int L=T.start[u];
int R=L+T.len[u]-1;
R=min(R,T.n);
int V=sum[R]-sum[L-1];
if(V)val[u]=1;
else{
V=sum2[R]-sum2[L-1];
if(V)val[u]=2;
}
return;
}
for(int i=0; i<28; ++i) {
if(T.ch[u][i]) {
predfs(T.ch[u][i],dep+T.len[T.ch[u][i]]);
val[u]|=val[T.ch[u][i]];
}
}
if(val[u]>=3)ans=max(ans,dep);
}
char buf[1<<21],*p1=buf,*p2=buf;
string read(){
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
string s="";
char ch=gc();
while(ch=='\n')ch=gc();
while(ch!='\n')s+=ch,ch=gc();
return s;
}
int main() {
// freopen("1.in","r",stdin);
// freopen("SP.out","w",stdout);
clock_t ST,ET;
ST=clock();
z=read();Z=read();
z+=(char)'a'+26;
z+=Z;z+=(char)'a'+27 ;
for(int i=0;i<z.size();++i)z[i]-='a',T.Extend(z[i]);
tot=z.size();
for(int i=1; i<=tot; ++i) {
sum[i]=sum[i-1]+(T.s[i]==26);
sum2[i]=sum2[i-1]+(T.s[i]==27);
}
predfs(1,0);
printf("%d\n",ans);
ET=clock();
// cout<<(double)(ET-ST)/CLOCKS_PER_SEC<<"s"<<endl;
return 0;
}

注意,如果在\(dfs\)里面来根据这条边的起点和终点暴力处理的话,这就是个\(n^2\)暴力。观察到,我们只需要在叶子的节点处理,而在叶子节点暴力处理的复杂度也不够优秀。

观察到,第一个字符串的终止符一定先于第二个字符串的终止符出现(如果有的话)。那么,根据前缀和,先判断第一个终止符,再判断第二个终止符即可。

最后时间复杂度是:

\(\text{We let D show the constant,then the complexity is O(D*N).N is the length of these strings.}\)

【题解】SP1811 LCS - Longest Common Substring的更多相关文章

  1. SP1811 LCS - Longest Common Substring

    \(\color{#0066ff}{ 题目描述 }\) 输入2 个长度不大于250000的字符串,输出这2 个字符串的最长公共子串.如果没有公共子串则输出0 . \(\color{#0066ff}{输 ...

  2. 「双串最长公共子串」SP1811 LCS - Longest Common Substring

    知识点: SAM,SA,单调栈,Hash 原题面 Luogu 来自 poj 的双倍经验 简述 给定两字符串 \(S_1, S_2\),求它们的最长公共子串长度. \(|S_1|,|S_2|\le 2. ...

  3. 【SP1811】LCS - Longest Common Substring

    [SP1811]LCS - Longest Common Substring 题面 洛谷 题解 建好后缀自动机后从初始状态沿着现在的边匹配, 如果失配则跳它的后缀链接,因为你跳后缀链接到达的\(End ...

  4. 后缀自动机(SAM) :SPOJ LCS - Longest Common Substring

    LCS - Longest Common Substring no tags  A string is finite sequence of characters over a non-empty f ...

  5. spoj1811 LCS - Longest Common Substring

    地址:http://www.spoj.com/problems/LCS/ 题面: LCS - Longest Common Substring no tags  A string is finite ...

  6. spoj 1811 LCS - Longest Common Substring (后缀自己主动机)

    spoj 1811 LCS - Longest Common Substring 题意: 给出两个串S, T, 求最长公共子串. 限制: |S|, |T| <= 1e5 思路: dp O(n^2 ...

  7. LCS - Longest Common Substring(spoj1811) (sam(后缀自动机)+LCS)

    A string is finite sequence of characters over a non-empty finite set \(\sum\). In this problem, \(\ ...

  8. SPOJ 1811 LCS - Longest Common Substring

    思路 和SPOJ 1812 LCS2 - Longest Common Substring II一个思路,改成两个串就有双倍经验了 代码 #include <cstdio> #includ ...

  9. SPOJ1811 LCS - Longest Common Substring(后缀自动机)

    A string is finite sequence of characters over a non-empty finite set Σ. In this problem, Σ is the s ...

随机推荐

  1. 你懂RocketMQ 的架构原理吗?

    前言 前面我们跟大家聊了聊什么是消息中间件,以及哪些场景使用哪些消息中间件更加合适. 我们了解到RocketMQ是java语言开发的,我们能更深入的阅读源码了解它的底层原理,而且它具有优秀的消息中间件 ...

  2. Selenium执行js脚本

    如何使用Selenium来执行Javascript脚本呢 Selenium中提供了一个方法:execute_script 来执行js脚本 return 可以返回js的返回结果 execute_scri ...

  3. android尺寸问题(转)

    android尺寸问题(转) (2013-01-15 16:55:36) 转载▼ 标签: 杂谈 分类: LINUX 最近公司做的项目中涉及到屏幕自适应的问题.由于做的是电视版的项目,因此屏幕自适应问题 ...

  4. 大概是win里最方便快捷的截图+拾色软件——Snipaste

    Snipaste 是一个简单而又强大的截图工具,还可以让你将截图贴回到屏幕上!按下 F1 截图快捷键来开始截图,再按 F3贴图快捷键,截图就在桌面置顶显示了.对比文字,对比表格,对比图片,就这么简单! ...

  5. 非旋Treap——fhq treap

    https://www.luogu.org/problemnew/show/P3369 知识点:1.拆分split,合并merge 2.split,merge要点:通过传址调用来简便代码 3.记得ro ...

  6. Nginx 路由--Location的使用

    一. 路由--Location的使用 9.1. Location语法规则 语法规则: location [=|~|~*|^~] /uri/ {… } 首先匹配 =,其次匹配^~,其次是按文件中顺序的正 ...

  7. 【漫话DevOps】What is DevOps?

    最近几年"DevOps"这个关键词经常出现在项目开发当中,特别是随着微服务/容器/cloud在项目中的大范围应用,你不想知道都很难.作为一个伴随CI/CD到DevOps一路走来的工 ...

  8. 循环删除list的方法

    错误的方法: 正确的方法:

  9. [计算机网络]图解HTTP阅读笔记

    总述 书的定位:一本十分浅显的HTTP书籍,主要介绍了HTTP与HTTPS.适合入门了解,很多地方都是蜻蜓点水,但稍微深入的地方能让人了解重点在哪,后面应该有针对性地阅读深入书籍. 主要内容:介绍了T ...

  10. 离线安装Superset 0.37

    上文提到了Superset 0.37的在线安装方式,只需要更新pip,然后pip install就可以了.但是在生产环境中,特别是内网环境中,很多时候是没有外网的,这时候就需要采取离线安装的方式. 本 ...