BZOJ5261 Rhyme 广义SAM

题目链接

题意:
给定n个由小写字母构成的模板串,给定k,求最长的字符串,使得该字符串每个长度为k的子串都是至少一个模板串的子串。求最大长度,如果可以无限长输出INF。 ∑ ∣ s ∣ < = 100 , 000 , k < = 100 , 000 ∑|s|<=100,000,k<=100,000 s<=100,000k<=100,000

题解:
考虑如何判断一个串符合条件:我们对于所有的模板串,建立一个广义SAM,在SAM上匹配前 k k k位,之后我们要尝试在原字符串后面加一个字符 c c c的时候,我们会在parent树上找到当前已经匹配到的点最近的一个有 c c c这个子节点的祖先节点,这个过程与AC自动机有点类似。我们能从这个点在parent树上往上跳若干步再往下走到下一个点的条件是那个祖先节点的 c c c这个孩子节点表示的串的长度也要大于等于 k k k,小于 k k k的话就意味着向前拓展仍然是模板串的子串的最大长度不到 k k k了,也就是在往前拓展到 k k k这个长度,就出现了一个长度为 k k k却不是原来模板串的子串的情况,就不合法了。

用一个类似AC自动机的fail指针的思想,如果一个点没有 c c c这个子节点,那么我们把这个点的 c c c这个子节点设为它parent树上最近的一个有这个字符的点的 c c c这个子节点。这样我们会形成一个DAG,我们在DAG上找最长路就是要求的答案,如果出现环答案就是INF。

最后说一下广义后缀自动机的事。
我在网上看到了很多建广义后缀自动机的说法,主要有三种。

第一种是先对于所有模板串建出trie树,然后在trie树上bfs,注意是要bfs,因为dfs的复杂度据说是可以被卡的,一边bfs一边加字符,同时要让后缀自动机的插入字符的函数返回一下插入当前字符之后的last,以保证后来的字符在插入的时候对应是last是正确的。

update:
网上有人说dfs的方法在一个串一个串地给出时是不能卡的。怎么卡,怎么不被卡暂时不知道。

第二种是像原来一样直接建,但是两个字符串之间用一个特殊符号分隔开,在算答案时有时需要特判一下是不是特殊字符。

第三种也是直接建,但是每建完一个字符串之后都把last的值重置为根节点。

我机房的同学说第三种可能有问题,他说他在之前有一个题那么写出过问题,但是我没有听懂他是怎么出问题的,也不排除他自己写错的可能。他说第一种写法是肯定没有错的。所以有明白的大佬欢迎与我交流这三种做法的正确性。

关于这个题,我是写的第三种建广义SAM的写法,我又上网上找了一个写第一种建法的人的代码,然后自己造了一些情况下的数据,在本机对拍,没有发现什么区别,并且我的第三种写法的代码在BZOJ上也是能过的。

update:
感谢Rayment的指正。这里暴力跳parent树复杂度是不对的,可以被卡成 n 2 n^2 n2,原因是可能一次只能往上跳一个点,你每次都需要 O ( n ) O(n) O(n)的往上跳,于是就卡成 n 2 n^2 n2了。

于是一个复杂度正确的建图方法是,我们其实想找的是每一个表示的串的长度大于等于 k k k的节点,我们假如在后面假如的字符是 c c c,那么其实要找的就是parent树上这个点祖先中深度最深的有 c c c这个字符的节点。于是可以从parent树的根开始dfs,一边搜索一边维护字符集大小个信息,分别表示在后面如果要假如 x x x这个字符的话应该跳到哪个节点。这样的复杂度就是对的了。代码就懒得改了。顺便放一下当时Rayment大佬的hack数据:
2   5 2\ 5 2 5
a a b aab aab
a a a . . . a aaa...a aaa...a(共99000个)

代码:

#include <bits/stdc++.h>
using namespace std;

int qq,n,k,ans,fa[800010],len[800010],ch[800010][26],lst=1,cnt=1,rt=1,du[800010],f[800010];
queue<int> q;
char s[100010];
inline void insert(int x)
{
	int cur=++cnt,pre=lst;
	lst=cur;
	len[cur]=len[pre]+1;
	for(;pre&&!ch[pre][x];pre=fa[pre])
	ch[pre][x]=cur;
	if(!pre)
	fa[cur]=rt;
	else
	{
		int ji=ch[pre][x];
		if(len[ji]==len[pre]+1)
		fa[cur]=ji;
		else
		{
			int gg=++cnt;
			fa[gg]=fa[ji];
			len[gg]=len[pre]+1;
			memcpy(ch[gg],ch[ji],sizeof(ch[ji]));
			fa[ji]=fa[cur]=gg;
			for(;pre&&ch[pre][x]==ji;pre=fa[pre])
			ch[pre][x]=gg;
		}
	} 
}
int main()
{
	while(~scanf("%d%d",&qq,&k))
	{
		memset(len,0,sizeof(len));
		memset(ch,0,sizeof(ch));
		memset(du,0,sizeof(du));
		memset(fa,0,sizeof(fa));
		memset(f,0,sizeof(f));
		ans=0;
		cnt=1;
		for(int ymh=1;ymh<=qq;++ymh)
		{
			scanf("%s",s+1);
			n=strlen(s+1);
			lst=1;
			for(int i=1;i<=n;++i)
			insert(s[i]-'a');
			for(int i=1;i<=n;++i)
			s[i]=0;	
		}
		int pd=0;
		for(int i=1;i<=cnt;++i)
		{
			if(len[i]>=k)
			{
				pd=1;
				for(int x=0;x<=25;++x)
				{
					if(ch[i][x]&&len[ch[i][x]]>=k)
					++du[ch[i][x]];				
					else
					{
						int pre=fa[i];
						for(;pre&&!ch[pre][x]&&len[pre]+1>=k;pre=fa[pre]);
						if(pre&&ch[pre][x]&&len[pre]+1>=k)
						{
							ch[i][x]=ch[pre][x];
							++du[ch[i][x]];
						}
					}
				}
			}
		}
		if(pd==0)
		{
			printf("%d\n",k-1);
			continue;
		}
		for(int i=1;i<=cnt;++i)
		{
			if(len[i]>=k&&!du[i])
			{
				f[i]=len[i];
				q.push(i);
			}			
		}
		if(q.empty())
		{
			printf("INF\n");
			continue;
		}
		while(!q.empty())
		{
			int x=q.front();
			q.pop();
			for(int i=0;i<=25;++i)
			{
				if(ch[x][i])
				{
					f[ch[x][i]]=max(f[ch[x][i]],f[x]+1);
					--du[ch[x][i]];
					if(!du[ch[x][i]])
					q.push(ch[x][i]);
				}
			}
		}
		for(int i=1;i<=cnt;++i)
		{
			if(len[i]>=k&&du[i])
			{
				ans=2e9;
				break;
			}
		}
		if(ans==2e9)
		{
			printf("INF\n");
			continue;
		}
		for(int i=1;i<=cnt;++i)
		ans=max(ans,f[i]);
		printf("%d\n",ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值