SP1811 Longest Common Substring(后缀自动机)

1、 两个字符串 s 和 t(假如 s的长度大于t),先构造s 的后缀自动机。
后缀自动机一个特点, 从根节点开始跑, 到达任何一个点,对应
s 的一个子串。
2、 对于t的每一个前缀 t[1~i], 都在 s 中寻找这个前缀 t[1~i] 的最长后缀。
方法是在s 的后缀自动机跑,用v 表示当前跑到的节点, lcs 表示当前匹配的长度。
现在添加一个字符 t[i] 并为其重新计算答案:
如果存在一个从v 到字符 t[i] 的转移,我们只需要转移并让 lcs++
如果不存在,v = fa[v], v点沿着后缀链接边回溯, 同时 lcs = len[v]
具体看代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e5 + 10;
char s[N], t[N], tmp[N];
int n;
ll cnt[N], ans;
int tot = 1, np = 1;
int fa[N], ch[N][26], len[N];

void extend(int c)
{
	// p回跳指针,np 新节点
	// q链接点,nq 新链接点
	int p = np; np = ++tot;				//p指向旧节点, np是新点
	len[np] = len[p] + 1; cnt[np] = 1;	//子串出现次数
	//p沿着链接边回跳,从旧节点向新点建转移边
	for(; p && !ch[p][c]; p = fa[p])
		ch[p][c] = np;

	//1、 如果c是新字符, 从新点向根节点建链接边
	if(p == 0)
		fa[np] = 1;
	else{
		int q = ch[p][c];	//q是链接点
		//2、 若链接点合法,从新点向链接点接边
		if(len[q] == len[p] + 1)
			fa[np] = q;

		//3、若链接边不合法, 则裂开q点,重建两类边
		else{
			int nq = ++tot;	//nq 是新建链接点
			len[nq] = len[p] + 1;
			//重建 nq, q, np 的链接边
			fa[nq] = fa[q]; fa[q] = nq; fa[np] = nq;
			
			// 指向q的转移边改为指向nq
			for(; p && ch[p][c] == q; p = fa[p])
				ch[p][c] = nq;

			//从q发出的转移边复制给 nq
			memcpy(ch[nq], ch[q], sizeof(ch[q]));
		}
	}
}

void solve()
{
	ll lcs = 0;
	int size = strlen(t + 1), v = 1;
	for(int i = 1; i <= size; ++i) 
	{
		int u = t[i] - 'a';			//遍历t的每一个字符 t[i]
		if(ch[v][u])				//匹配上了, lcs++, 同时更新 ans
		{
			lcs++;
			ans = max(ans, lcs);
			v = ch[v][u];
		}else{
			while(fa[v])			// 如果后缀自动机当前点v 没有匹配上, v点就沿着后缀链接边回退
			{
				v = fa[v];
				lcs = len[v];		// 同时更新lcs, 等于v点 的 fa[v] 的 len值
				if(ch[v][u])		// 回退过程中,能匹配上, lcs++, 同时更新 ans。 然后退出循环
				{
					lcs++;
					ans = max(ans, lcs);
					v = ch[v][u];
					break;
				}
			}
		}
	}
}

int main()
{
	scanf("%s", s + 1);
	scanf("%s", t + 1);
	if(strlen(s + 1) < strlen(t + 1))
	{
		strcpy(tmp + 1, s + 1);
		strcpy(s + 1, t + 1);
		strcpy(t + 1, tmp + 1);
	}
	n = strlen(s + 1);
	for(int i = 1; i <= n; ++i)
	{
		extend(s[i] - 'a');
	}
	solve();
	printf("%lld\n", ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值