POJ 2774 Long Long Message —— 后缀自动机求两个串的最长公共子串

This way

题意:

给你两个串,问你他们的最长公共子串的长度为多少

题解:

后缀自动机,大致细节我都写在注释里面了
还算可以的时间复杂度
在这里插入图片描述

#include<iostream>
#include<stdio.h>
#include<cstring>
using namespace std;
#define ll long long
const int N = 2e5+1000;
char s[N];
int k,lens;
struct SAM{
	int last,cnt,nxt[N*2][26],fa[N*2],l[N*2],num[N*2];
	int lasrt;//表示当前字符串所匹配的字典树的位置
	void init(){
		last = cnt=1;
		memset(nxt[1],0,sizeof nxt[1]);
		fa[1]=l[1]=num[1]=0;
		lasrt=1;
	}
	int inline newnode(){
		cnt++;
		memset(nxt[cnt],0,sizeof nxt[cnt]);
		fa[cnt]=l[cnt]=num[cnt]=0;
		return cnt;
	}
	void add(int c){
		int p = last;
        int np = newnode();
        last = np;
        l[np] =l[p]+1;
        while (p&&!nxt[p][c]){
            nxt[p][c] = np;
            p = fa[p];
        }
        if (!p){
            fa[np] =1;
        }
        else{
            int q = nxt[p][c];
            if (l[q]==l[p]+1){
                fa[np] =q;
            }
            else{
                int nq = newnode();
                memcpy(nxt[nq],nxt[q],sizeof nxt[q]);
                fa[nq] =fa[q];
                l[nq] = l[p]+1;
                fa[np] =fa[q] =nq;
                while (nxt[p][c]==q){
                    nxt[p][c]=nq;
                    p = fa[p];
                }
            }
        }
        int tmp=last;
        while(tmp&&!num[tmp])
            num[tmp]|=1,tmp=fa[tmp];
            /*
                如果这个点之前已经被访问过了,就说明它以及所有父类都被访问过
                那么直接停止
                否则就是n*n的时间复杂度
            */
	}
	void back_id(int len,int bin)//当前面有一个字符被删掉的时候跳回去
	{
	    while(lasrt&&l[fa[lasrt]]>=len)
            lasrt=fa[lasrt];
            /*
                因为l[fa[a]]<l[a],所以可以用这个性质跳到父类.
            */
        lasrt=lasrt?lasrt:1;
        //这里需要判断一下因为会len会<=0
        num[lasrt]|=(1<<bin);//用二进制就可以做最大范围是63的最长公共子串
	}
	void add_id(int len,int c,int bin)//新进来字符时跳到下一个点
	{
	    lasrt=nxt[lasrt][c];
	    //因为已经必然有这个方向的字典树了,所以不需要判断是否为0
	    back_id(len,bin);
	}
	int is_substr(int nexc)
	{
	    return nxt[lasrt][nexc];
	}
}sam;
char ss[N];
ll dp[N];
int main()
{
    //scanf("%d",&k);
    scanf("%s",s);
    int len=strlen(s);
    sam.init();
    for(int i=0;i<len;i++)
        sam.add(s[i]-'a');
    int mx=0;
    sam.lasrt=1;
    scanf("%s",s);
    len=strlen(s);
    int r=0;
    for(int j=0;j<len;j++)
    {
        while(r<=j&&!sam.is_substr(s[j]-'a'))
        {
            r++;
            sam.back_id(j-r,1);
            //由于最前面的字符被删掉了,所以需要跳到父亲类中,为了查看接下来是否可以重新连边
            //为什么是i-r是因为要跳到父类中呀,i-r+1就可能跳到子类中了
        }
        sam.add_id(j-r+1,s[j]-'a',1);
        //如果跳到了根或者已经有到i的后缀了,那么就更新自动机中的尾指针
        if(sam.num[sam.lasrt]==3&&j-r+1>mx)
            mx=j-r+1;
    }
    printf("%d\n",mx);

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值