SPOJ 1812 LCS2 - Longest Common Substring II (后缀自动机)【两种做法】

人生第一道后缀自动机。
说实话SAM我还没学多么明白。
但是题还是要做的。
说起来这玩意真的很妙。可惜我智商低理解不了。
再次验证了代码能力菜到没边。hyw 30min写完我写2.5h.

题目链接 (洛谷)
https://www.luogu.org/problemnew/show/SP1812

题目大意
n n n个长度为 l i l_i li的小写字母字符串,求它们的最长公共子串 (要求每个字符串都要出现。) n ≤ 10 , l i ≤ 1 0 5 n\le 10, l_i\le 10^5 n10,li105

题解
做法一 二分+hash判断。 O ( L log ⁡ L × n ) O(L\log L\times n) O(LlogL×n), 据说会TLE.
做法二 后缀数组。不会。
做法三 后缀自动机。
用后缀自动机的有两种做法
做法一
首先考虑如何求两个串 A , B A,B A,B L C S LCS LCS. 对 A A A串建出后缀自动机,用 B B B串在上面匹配。
匹配时从头到尾枚举 B B B的每一个字符,记录当前 A A A串后缀自动机的位置 p o s pos pos以及当前长度 l e n len len, 初始 p o s = r t n , l e n = 0 pos=rtn, len=0 pos=rtn,len=0 ( r t n rtn rtn为根节点)

  1. 当前存在一条匹配边。即son[pos][str[i]]!=0, 则 p o s pos pos跳到 s o n [ p o s ] [ s t r [ i ] ] son[pos][str[i]] son[pos][str[i]], l e n len len增加1即可。
  2. 当前不存在一条匹配便。即son[pos][str[i]]==0, 则 p o s pos pos u u u开始向上跳,直到u==0或者son[u][str[i]]==0.分别对应第3和1种情况。此时 l e n len len应置为 L e n [ p o s ] Len[pos] Len[pos], L e n [ u ] Len[u] Len[u]表示 u u u状态表示的最长字符串。
  3. 如果u==0表明我们匹配到了自动机外面,则此时应重置 p o s = r t n , l e n = 0 pos=rtn,len=0 pos=rtn,len=0
    这样我们可以处理 n = 2 n=2 n=2的情形。 n > 2 n>2 n>2?
    对于 A 2 , A 3 , . . . , A n A_2, A_3,...,A_n A2,A3,...,An分别跑一次,每一个节点记录一下匹配大小的最小值,然后求最大即可。每个点记录的最小值是因为要求这个子串同时是这 n n n个串的公共部分,求最大是对于合法的状态求出最大值。
    做完了。?
    少了一步
    观察到 n = 2 n=2 n=2 A A A若长度较长的子串可以匹配,那么长度较短的子串也可以匹配。因此我们需要每做完一个串进行一遍更新:
if(fa[u]) mx[fa[u]] = max(mx[fa[u]],mx[u]);

做完了吗?
我们发现实际上对每个点还有限制,就是 m x [ u ] ≤ l e n [ u ] mx[u]\le len[u] mx[u]len[u].

if(fa[u]) {mx[fa[u]] = max(mx[fa[u]],min(mx[u],len[fa[u]]));}

真·做完了。

代码

//Wrong Coding:
//pos = fa[pos]; curl = len[pos]; Wrong Order
//insertstr() len[np]=len[p]+1 Forgot
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define llong long long
using namespace std;

const int N = 2e5;
const int M = 3e5;
const int S = 26;
int son[N+3][S+3];
int fa[N+3];
int len[N+3];
char a[N+3];
char b[N+3];
int buc[N+3];
int oid[N+3];
int ans[N+3];
int mx[N+3];
int sz[N+3];
int lpos,siz,rtn,lena;

void insertstr(char ch)
{
    int p = lpos,np; siz++; np = lpos = siz; sz[np] = 1; len[np] = len[p]+1;
    for(; p && son[p][ch]==0; p=fa[p]) son[p][ch] = np;
    if(p==0) {fa[np] = rtn;}
    else
    {
        int q = son[p][ch];
        if(len[p]+1==len[q]) {fa[np] = q;}
        else
        {
            siz++; int nq = siz; len[nq] = len[p]+1;
            memcpy(son[nq],son[q],sizeof(son[q]));
            fa[nq] = fa[q]; fa[np] = fa[q] = nq;
            for(; p!=0 && son[p][ch]==q; p=fa[p]) son[p][ch] = nq;
        }
    }
}

void prework()
{
    for(int i=1; i<=siz; i++) buc[len[i]]++;
    for(int i=1; i<=lena; i++) buc[i] += buc[i-1];
    for(int i=siz; i>=1; i--) oid[buc[len[i]]--] = i;
    for(int i=siz; i>=1; i--)
    {
        int pos = oid[i];
        sz[fa[pos]] += sz[pos];
    }
}

void dfs(char str[],int lens)
{
    int curl = 0,pos = rtn;
    for(int i=1; i<=lens; i++)
    {
        while(pos && son[pos][str[i]-96]==0) {pos = fa[pos]; curl = len[pos];}
        if(pos) {curl++; pos = son[pos][str[i]-96]; mx[pos] = max(mx[pos],curl);}
        else {pos = rtn; curl = 0;}
    }
    for(int i=siz; i>=1; i--)
    {
        int u = oid[i];
        if(fa[u]) {mx[fa[u]] = max(mx[fa[u]],min(mx[u],len[fa[u]]));}
        ans[u] = min(ans[u],mx[u]);
        mx[u] = 0;
    }
}

int main()
{
    memset(ans,1,sizeof(ans));
    siz = 1; rtn = 1; lpos = 1;
    scanf("%s",a+1);
    lena = strlen(a+1);
    for(int i=1; i<=lena; i++) insertstr(a[i]-96);
    prework();
    while(scanf("%s",b+1)!=EOF)
    {
        int lenb = strlen(b+1);
        dfs(b,lenb);
    }
    int fans = 0;
    for(int i=1; i<=siz; i++) {if(ans[i]<=5e6) fans = max(fans,ans[i]);}
    printf("%d\n",fans);
    return 0;
}

完了?没有呢,还有做法二。
做法二
我们考虑这个题的一个加强版:每次给 n n n个串的一个子集,询问这个子集内的串的 L C S LCS LCS. n ≤ 20 , L ≤ 1 0 5 , q ≤ 1 0 5 n\le 20, L\le 10^5, q\le 10^5 n20,L105,q105
做法: n n n个串并起来建立广义 S A M SAM SAM. 然后每个状态记录一个 n n n位二进制数。对于第 i i i个串先在其所到达状态标记 2 i 2^i 2i. 最后把标记沿着Parent树从下往上更新一下,然后统计出每一个子集的答案,然后再用每个集合的答案更新它的子集的答案(注意避免 O ( 3 n ) O(3^n) O(3n) T L E TLE TLE)即可。
时间复杂度 O ( 2 n n + n L ) O(2^nn+nL) O(2nn+nL)
可以参考GDOI2017微信这道题。见DC巨佬的博客:https://www.cnblogs.com/dcdcbigbig/p/10135665.html (引用已经过博主同意orz)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值