SPOJ最长公共子串(SAM)

题意

n个字符串的最长公共子串。( n ≤ 10 , l e n ( s i ) ≤ 1 0 5 n \le 10,len(s_i) \le 10^5 n10,len(si)105)

思路

对第一个字符串建SAM,后面每读入一个串就把这个串在SAM上面跑,跑不动了就沿着 p a r e n t _ t r e e parent \_ tree parent_tree往上跳。每一个状态上取min,答案是所有状态取max。

注意

虽然是模板题但是还是WA了一下午。

1

让一个字符串在SAM上跑的代码如下:

void solve(char *s, int sz)
{
	int u = 1, l = 0;
	for (int i = 0; i < sz; ++ i){
		int x = s[i]-'a';
		if (ch[u][x]) ++ l, u = ch[u][x];
		else{
			while (u && !ch[u][x]) u = fa[u];
			if (!u) u = 1, l = 0;
			else l = len[u]+1, u = ch[u][x];
		}
		now[u] = max(now[u], l);
	}
}

然后这是错误写法:

void solve(char *s, int sz)
{
	int u = 1, v;
	for (int i = 0; i < sz; ++ i){
		int x = s[i]-'a';
		while (fa[u] && !ch[u][x]) u = fa[u], now[u] = len[u];
		v = ch[u][x];
		if (v) now[v] = max(now[v], now[u]+1), u = v;
		else u = 1;
	}
}

区别在哪里呢?

可以看出第二种写法并没有用到变量 l l l,而是每次用 n o w [ u ] now[u] now[u]去更新 n o w [ v ] now[v] now[v]。然而很显然, n o w [ u ] now[u] now[u]指的是以当前节点结尾的最长子串, l l l指的是当前已经匹配的子串长度,在几次失配之后, n o w [ u ] now[u] now[u] l l l可能不相等。

比如这个样例:
SAM里面是aabc
待匹配的字符串是aababc
那么跑到第二个b的时候 l = 2 , n o w [ u ] = 3 l=2,now[u]=3 l=2,now[u]=3,出锅。

2

沿着 p a r e n t _ t r e e parent\_tree parent_tree向上跳的时候,爬到的节点 l = l e n [ u ] l=len[u] l=len[u]。因为在 p a r e n t _ t r e e parent\_tree parent_tree上,父亲节点包含的任意子串均是儿子任意子串的后缀,所以假如能够跑到儿子,那么他父亲所包含的所有子串均是当前已匹配子串的后缀,所以能够匹配。

那么在什么情况下会出现 n o w [ f a [ u ] ] &lt; l e n [ u ] now[fa[u]] &lt; len[u] now[fa[u]]<len[u]呢?

看一组样例:
SAM里的字符串:abcbcc
需要识别的字符串:abcc

跑完前3个字符时,处在 r i g h t ( u ) = { 3 } right(u)=\{3\} right(u)={3}的节点 u u u,此时下一个字符c失配了,跳到 r i g h t ( v ) = { 3 , 5 } right(v)=\{3,5\} right(v)={3,5},但是此时 v v v这个节点还从来没有被访问过, n o w ( v ) = 0 now(v)=0 now(v)=0,出锅。

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10, PN = N<<1, S = 26;
namespace SAM
{
	int fa[PN], ch[PN][S], len[PN], cnt, last;
	inline void reset(){
		last = cnt = 1;
	}
	inline void copy(int x, int y){
		for (int i = 0; i < S; ++ i)
			ch[y][i] = ch[x][i];
		fa[y] = fa[x];
	}
	inline void extend(int x){
		x -= 'a';
		int p = last, np = ++cnt;
		last = np;
		len[np] = len[p]+1;
		while (p && !ch[p][x]) ch[p][x] = np, p = fa[p];
		if (!p) fa[np] = 1;
		else{
			int q = ch[p][x];
			if (len[q] == len[p]+1) fa[np] = q;
			else{
				int nq = ++cnt;
				copy(q, nq);
				len[nq] = len[p]+1;
				fa[q] = fa[np] = nq;
				while (p && ch[p][x] == q) ch[p][x] = nq, p = fa[p];
			}
		}
	}
}
using namespace SAM;
int k, ans[PN], now[PN], id[PN], anss;
char s[N];

bool cmp(int x, int y){
	return len[x] < len[y];
}

void solve(char *s, int sz)
{
	int u = 1, l = 0;
	for (int i = 0; i < sz; ++ i){
		int x = s[i]-'a';
		if (ch[u][x]) ++ l, u = ch[u][x];
		else{
			while (u && !ch[u][x]) u = fa[u];
			if (!u) u = 1, l = 0;
			else l = len[u]+1, u = ch[u][x];
		}
		now[u] = max(now[u], l);
	}
}

int main()
{
	scanf("%s", s);
	reset();
	for (int i = 0, sz = strlen(s); i < sz; ++ i)
		extend(s[i]);
	memset(ans, 0x7f, sizeof(ans));
	for (int i = 1; i <= cnt; ++ i) id[i] = i;
	sort(id+1, id+cnt+1, cmp);
	while (~scanf("%s", s)){
		if (s[0] == '0') break;
		memset(now, 0, sizeof(now));
		solve(s, strlen(s));
		for (int j = cnt; j >= 1; -- j){
			int x = id[j];
			ans[x] = min(ans[x], now[x]);
			if (fa[x] && now[x]) now[fa[x]] = len[fa[x]];
		}
	}
	anss = 0;
	for (int i = 1; i <= cnt; ++ i)
		anss = max(anss, ans[i]);
	printf("%d\n", anss);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值