String

String

题解

还是因为某些原因,这里用英文名。

首先一看到找最长公共子串,我们就想到了后缀自动机。
我们先对每个字符串,建一棵SAM
对于每个询问,就将两棵SAM进行匹配,由于每次匹配实际上是将两个DAG匹配,由于是DAG,我们最好记忆化一下。
每次匹配的时间复杂度大概是 O ( α s i z ( 较 小 子 树 ) ) O\left(\alpha siz(较小子树)\right) O(αsiz())

由于总字符串长度与 n n n同阶,我们总时间复杂度为 O ( α q n ) O\left(\alpha q\sqrt{n}\right) O(αqn )
现在,你将这个暴力直接交上去只会有68pts。
考虑对 α \alpha α进行优化,这个 a l p h a alpha alpha包含两个方面,一是记忆化,二是枚举下一个转移。
记忆化我们可以采用unordered_map,对于转移又该如何优化呢?
我们知道,大部分 S A M SAM SAM上的点能够转移的点是比较少的,匹配两个点时,我们是针对它们都有的边进行转移。一个点的总出边不会超过 26 26 26条,考虑状压。
但我们发现 2 26 = 67108864 2^{26}=67108864 226=67108864,明显会MLE。
我们可以用类似链向星的形式,记录下一个可转移的点,当两边一样是再匹配。

当我们这样对 α \alpha α优化后,还是只有76pts,考虑对其它地方进行优化。
复杂度基本无法优化了,想想出题人会怎样造数据。
能卡人的数据一定是长串与短串特别分明,即长串尽可能的多。
于是,短串能得到的长度就特别的小,而总共又只有26个字母。
所以能卡人的数据为了让人尽可能地向下,一定会有很多相同的串。
于是,我们可以将相同的串hash去掉,再进行询问。
现在,你就成功卡过了。

这又是什么奇技淫巧呀!

源码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#include<unordered_map>
using namespace std;
#define MAXN 160005
#define MAXM 50005
#define lowbit(x) (x&-x)
#define reg register
typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int,int> pii;
const int INF=0x7f7f7f7f;
const LL mod1=1e9+9;
const LL mod2=998244353;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
int n,q,tot,id[MAXM],leng[MAXM];char str[MAXM];
unordered_map<LL,int> S;
unordered_map<LL,int> Hs;
struct ming{int ch[27],fa,len,nxt[27],head;}a[MAXN];
struct SAM{
	int root,las,sz;
	void init(){las=root=++tot;}
	void extend(const int x){
		int p=las,np=las=++tot;a[np].len=a[p].len+1;
		for(;p&&!a[p].ch[x];p=a[p].fa)a[p].ch[x]=np;
		if(!p){a[np].fa=root;return ;}int q=a[p].ch[x];
		if(a[q].len==a[p].len+1){a[np].fa=q;return ;}
		int nq=++tot;a[nq]=a[q];a[nq].len=a[p].len+1;a[q].fa=a[np].fa=nq;
		for(;p&&a[p].ch[x]==q;p=a[p].fa)a[p].ch[x]=nq;
	}
	void solve(){
		for(int i=root;i<=tot;++i){
			int now=26;
			for(int j=25;j>=0;--j){
				a[i].nxt[j]=now;
				if(a[i].ch[j])now=j;
			} 
			a[i].head=now;
		}
		sz=tot-root+1;
	}
}T[MAXM];
int sakura(const int u,const int v){
	const LL S1=1ll*u*tot+1ll*v;
	if(S.find(S1)!=S.end())return S[S1];int res=0;
	for(int i=max(a[u].head,a[v].head);i<26;){
		if(a[u].ch[i]&&a[v].ch[i])
			res=max(res,sakura(a[u].ch[i],a[v].ch[i])+1);
		i=max(a[u].nxt[i],a[v].nxt[i]);	
	}
	S[S1]=res;return res;
}
signed main(){
	read(n);read(q);int sum=0;
	for(int i=1;i<=n;i++){
		T[i].init();scanf("%s",str+1);int len=strlen(str+1),now1=0,now2=0;leng[i]=len;id[i]=i;
		for(int j=1;j<=len;j++)now1=(233ll*now1+1ll*str[j])%mod1,now2=(233ll*now2+1ll*str[j])%mod2;
		LL tmp=1ll*now1*mod2+1ll*now2;if(Hs[tmp]){id[i]=Hs[tmp];continue;}else Hs[tmp]=i;
		for(int j=1;j<=len;j++)T[i].extend(str[j]-'a');T[i].solve();sum+=len;
	}
	for(int i=1;i<=q;i++){
		int u,v,ans;read(u);read(v);u++;v++;u=id[u];v=id[v];
		if(u==v){printf("%d\n",leng[u]);continue;}
		if(T[u].sz>T[v].sz)swap(u,v);u=T[u].root;v=T[v].root;
		ans=sakura(v,u);printf("%d\n",ans);
	}
	return 0;
}


posted @ 2021-02-19 20:44  StaroForgin  阅读(15)  评论(0)    收藏  举报  来源