后缀自动机的一些题目的整理

后缀自动机的一些题目的整理

P3804 【模板】后缀自动机(SAM)

P3804 【模板】后缀自动机(SAM)

板子题,没啥好说的。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+5;

int tot=1,last=1,idx,n;
int ver[N],h[N],ne[N];
char s[N];
ll f[N],ans;
struct node
{
	int len,fa;
	int ch[26];
}a[N];

void extend(int c)
{
	int p=last,np=++tot;
	last=np;
	f[tot]=1;
	a[np].len=a[p].len+1;
	while(p&&!a[p].ch[c])
	{
		a[p].ch[c]=np;
		p=a[p].fa;
	}
	if(!p) a[np].fa=1;
	else 
	{
		int q=a[p].ch[c];
		if(a[q].len==a[p].len+1) a[np].fa=q;
		else 
		{
			int nq=++tot;
			a[nq]=a[q];
			a[nq].len=a[p].len+1;
			a[q].fa=a[np].fa=nq;
			while(p&&a[p].ch[c]==q) 
			{
				a[p].ch[c]=nq;
				p=a[p].fa;
			}
		}
	}
}
void add(int x,int y)
{
	ver[++idx]=y,ne[idx]=h[x],h[x]=idx;
}
void dfs(int u)
{
	for(int i=h[u];i;i=ne[i])
	{
		dfs(ver[i]);
		f[u]+=f[ver[i]];
	}
	if(f[u]>1) ans=max(ans,f[u]*a[u].len);
}
int main ()
{
	cin>>s;
	n=strlen(s);
	for(int i=0;i<n;i++) extend(s[i]-'a');
	for(int i=2;i<=tot;i++) add(a[i].fa,i);
	dfs(1);
	cout<<ans<<"\n";
	return 0;
}

P5231 [JSOI2012] 玄武密码

P5231 [JSOI2012] 玄武密码

类似于 \(trie\) 的查找,比较简单。

#include <bits/stdc++.h>
using namespace std;
const int N=1e7+5;
int n,m;
int tot=1,last=1;
struct node
{
	int len,fa;
	int ch[4];
}a[N*2];
char s[N];
string ss;
void extend(int c)
{
	int p=last,np=++tot;
	last=np;
	a[np].len=a[p].len+1;
	while(p&&!a[p].ch[c])
	{
		a[p].ch[c]=np;
		p=a[p].fa;
	}
	if(!p) a[np].fa=1;
	else 
	{
		int q=a[p].ch[c];
		if(a[q].len==a[p].len+1) a[np].fa=q;
		else 
		{
			int nq=++tot;
			a[nq]=a[q];
			a[nq].len=a[p].len+1;
			a[q].fa=a[np].fa=nq;
			while(p&&a[p].ch[c]==q) 
			{
				a[p].ch[c]=nq;
				p=a[p].fa;
			}
		}
	}
}
map<char,int> ma;
int main ()
{
	ma['N']=0,ma['S']=1,ma['W']=2,ma['E']=3;
	cin>>n>>m;
	scanf("%s",s);
	for(int i=0;s[i];i++) extend(ma[s[i]]);
	while(m--)
	{
		scanf("%s",s);
		int p=1,ret=0;
		for(int i=0;s[i];i++)
		{
			int t=ma[s[i]];
			if(a[p].ch[t]) p=a[p].ch[t],ret++;
			else break;
		}
		cout<<ret<<"\n";
	}
	return 0;
}

P5341 [TJOI2019] 甲苯先生和大中锋的字符串

P5341 [TJOI2019] 甲苯先生和大中锋的字符串

求出现 \(k\) 次的子串中出现次数的最多的长度。

用差分数组统计出现的次数。

#include <bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,t,k,ans;
int tot=1,last=1;
int f[N],d[N];
struct node
{
	int len,fa;
	int ch[26];
}a[N];
char s[N];
void extend(int c)
{
	int p=last,np=++tot;
	last=np;
	f[tot]=1;
	a[np].len=a[p].len+1;
	while(p&&!a[p].ch[c])
	{
		a[p].ch[c]=np;
		p=a[p].fa;
	}
	if(!p) a[np].fa=1;
	else 
	{
		int q=a[p].ch[c];
		if(a[q].len==a[p].len+1) a[np].fa=q;
		else 
		{
			int nq=++tot;
			a[nq]=a[q];
			a[nq].len=a[p].len+1;
			a[q].fa=a[np].fa=nq;
			while(p&&a[p].ch[c]==q) 
			{
				a[p].ch[c]=nq;
				p=a[p].fa;
			}
		}
	}
}
int ver[N],ne[N],h[N],idx;
void add(int x,int y)
{
	ver[++idx]=y,ne[idx]=h[x],h[x]=idx;
}
void dfs(int x)
{
	for(int i=h[x];i!=-1;i=ne[i])
	{
		dfs(ver[i]);
		f[x]+=f[ver[i]];
	}
	if(f[x]==k&&a[x].len) d[a[a[x].fa].len+1]++,d[a[x].len+1]--;
}
void clear()
{
	tot=last=1;
	idx=0;
	ans=-1;
	memset(d,0,sizeof d);
	memset(a,0,sizeof a);
	memset(f,0,sizeof f);
	memset(h,-1,sizeof h);
}
void solve()
{
	clear();
	cin>>s;
	n=strlen(s);
	cin>>k;
	for(int i=0;s[i];i++) extend(s[i]-'a');
	for(int i=2;i<=tot;i++) add(a[i].fa,i);
	dfs(1);
	int mx=1;
	for(int i=0;s[i];i++) 
	{
		d[i+1]+=d[i];
		if(d[i+1]>=mx)
		{
			mx=d[i+1];
			ans=i+1;
		}
	}
	if(ans==0) ans=-1;
	cout<<ans<<"\n";
}
int main ()
{
	cin>>t;
	while(t--) solve();	
	return 0;
}

LONGCS - Longest Common Substring

LONGCS - Longest Common Substring

对第一个字符串建立 \(SAM\),然后让后面的字符串在上面跑。类似于 \(KMP\),不匹配就跳到父亲,匹配就往下跳,并更新答案。最后拓扑排序统计答案。

#include <bits/stdc++.h>
using namespace std;
const int N=2e4+5;

int n,m;
int tot=1,last=1;
struct node
{
	int len,fa;
	int ch[26];
}a[N];
int ans[N],now[N];
int ver[N],ne[N],h[N],idx;
char s[N];
void extend(int c)
{
	int p=last,np=++tot;
	last=np;
	a[np].len=a[p].len+1;
	while(p&&!a[p].ch[c])
	{
		a[p].ch[c]=np;
		p=a[p].fa;
	}
	if(!p) a[np].fa=1;
	else 
	{
		int q=a[p].ch[c];
		if(a[q].len==a[p].len+1) a[np].fa=q;
		else 
		{
			int nq=++tot;
			a[nq]=a[q];
			a[nq].len=a[p].len+1;
			a[q].fa=a[np].fa=nq;
			while(p&&a[p].ch[c]==q) 
			{
				a[p].ch[c]=nq;
				p=a[p].fa;
			}
		}
	}
}
void add(int x,int y)
{
	ver[++idx]=y,ne[idx]=h[x],h[x]=idx;
}
void dfs(int x)
{
	for(int i=h[x];i!=-1;i=ne[i])
	{
		dfs(ver[i]);
		now[x]=max(now[x],now[ver[i]]);
	}
}
void solve()
{
	tot=1,last=1,idx=0;
	memset(a,0,sizeof a);
	cin>>n;
	cin>>s;
	for(int i=0;s[i];i++) extend(s[i]-'a');
	for(int i=1;i<=tot;i++) ans[i]=a[i].len;
	memset(h,-1,sizeof h);
	for(int i=2;i<=tot;i++) add(a[i].fa,i);
	for(int i=1;i<n;i++)
	{
		cin>>s;
		memset(now,0,sizeof now);
		int p=1,t=0;
		for(int j=0;s[j];j++)
		{
			int c=s[j]-'a';
			while(p>1&&!a[p].ch[c]) p=a[p].fa,t=a[p].len;
			if(a[p].ch[c]) p=a[p].ch[c],t++;
			now[p]=max(now[p],t);
		}
		dfs(1);
		for(int j=1;j<=tot;j++) ans[j]=min(ans[j],now[j]);
	}
	int res=0;
	for(int i=1;i<=tot;i++) res=max(res,ans[i]);
	cout<<res<<"\n";
}
int main ()
{
	int t;
	cin>>t;
	while(t--) solve();
	return 0;
}

NSUBSTR - Substrings

NSUBSTR - Substrings

求长度为 \(x\) 的子串的最大出现次数。

不需要用数据结构来处理 \(RMQ\),只需要倒着统计就行了。

因为一个字符串如果包含最长的字串有\(|endpos(u)|\)次,那么它包含更短的子串肯定有\(|endpos(u)|\)

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int tot=1,last=1;
struct node
{
	int len,fa;
	int ch[26];
}a[N];
char s[N];
int f[N],ans[N];
void extend(int c)
{
	int p=last,np=++tot;
	last=np;
	f[tot]=1;
	a[np].len=a[p].len+1;
	while(p&&!a[p].ch[c])
	{
		a[p].ch[c]=np;
		p=a[p].fa;
	}
	if(!p) a[np].fa=1;
	else 
	{
		int q=a[p].ch[c];
		if(a[q].len==a[p].len+1) a[np].fa=q;
		else 
		{
			int nq=++tot;
			a[nq]=a[q];
			a[nq].len=a[p].len+1;
			a[q].fa=a[np].fa=nq;
			while(p&&a[p].ch[c]==q) 
			{
				a[p].ch[c]=nq;
				p=a[p].fa;
			}
		}
	}
}
int ver[N],ne[N],h[N],idx;
void add(int x,int y)
{
	ver[++idx]=y,ne[idx]=h[x],h[x]=idx;
}
void dfs(int x)
{
	for(int i=h[x];i!=-1;i=ne[i])
	{
		dfs(ver[i]);
		f[x]+=f[ver[i]];
	}
	ans[a[x].len]=max(ans[a[x].len],f[x]);
}
int main ()
{
	cin>>s;
	int n=strlen(s);
	for(int i=0;s[i];i++) extend(s[i]-'a');
	memset(h,-1,sizeof h);
	for(int i=2;i<=tot;i++) add(a[i].fa,i);
	dfs(1);
	for(int i=n;i>=1;i--) ans[i]=max(ans[i],ans[i+1]);
	for(int i=1;i<=n;i++) cout<<ans[i]<<"\n";
	return 0;
}

P3975 [TJOI2015] 弦论

P3975 [TJOI2015] 弦论

建立 \(SAM\) 后从根开始往后跑,\(dfs\) 统计该节点后面存在的自串的数量。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+5;
ll n,k,t;
char s[N];
int tot=1,last=1;
struct node
{
	int len,fa;
	int ch[26];
}a[N];
ll f[N],siz[N];
void extend(int c)
{
	int p=last,np=++tot;
	last=np;
	siz[np]=1;
	a[np].len=a[p].len+1;
	while(p&&!a[p].ch[c])
	{
		a[p].ch[c]=np;
		p=a[p].fa;
	}
	if(!p) a[np].fa=1;
	else 
	{
		int q=a[p].ch[c];
		if(a[q].len==a[p].len+1) a[np].fa=q;
		else 
		{
			int nq=++tot;
			a[nq]=a[q];
			a[nq].len=a[p].len+1;
			a[q].fa=a[np].fa=nq;
			while(p&&a[p].ch[c]==q)
			{
				a[p].ch[c]=nq;
				p=a[p].fa;
			}
		}
	}
}
int ver[N],ne[N],h[N],idx;
void add(int x,int y)
{
	ver[++idx]=y,ne[idx]=h[x],h[x]=idx;
}
void dfs1(int x)
{
	for(int i=h[x];i;i=ne[i])
	{
		int y=ver[i];
		dfs1(y);
		siz[x]+=siz[y];
	}
	f[x]=siz[x];
}
int vis[N];
ll dfs2(int x)
{
	if(vis[x]) return f[x];
	vis[x]=1;
	for(int i=0;i<26;i++)
	{	
		int y=a[x].ch[i];
		if(y) f[x]+=dfs2(y);
	}
	return f[x];
}
void query(int x,ll k)
{
	if(k<=siz[x]) return ;
	k-=siz[x];
	for(int i=0;i<26;i++)
	{
		int y=a[x].ch[i];
		if(k>f[y]) k-=f[y];
		else 
		{
			cout<<(char)('a'+i);
			query(y,k);
			return ;
		}
	}
}
int main ()
{
	cin>>s;
	n=strlen(s);
	cin>>t>>k;
	if(n*(n+1)/2<k) 
	{
		cout<<-1<<"\n";
		return 0;
	}
	for(int i=0;s[i];i++) extend(s[i]-'a');
	for(int i=2;i<=tot;i++) add(a[i].fa,i);
	if(t) dfs1(1);
	else for(int i=1;i<=tot;i++) f[i]=siz[i]=1;
	f[1]=siz[1]=0;
	dfs2(1);
	query(1,k);
	return 0;
}

P3649 [APIO2014] 回文串

P3649 [APIO2014] 回文串

最恶心的一道题,\(manacher+SAM+倍增\),这样子写应该有黑题的难度,只是因为这题是回文自动机的板子题,所以是紫题。

\(manacher\) 找到回文串后在 \(SAM\) 上找出现次数。可以从根开始跑到回文串的 \(endpos\),但这样复杂度为平方级。于是考虑倍增向上跳,往上跳是到当前字符串的后缀上,所以查 \((l.r)\),从 \(r\) 开始往上跳。

代码细节非常的多。

#include <bits/stdc++.h>
using namespace std;
const int N=6e5+5;
typedef long long ll;
int tot=1,last=1;
int n,m;
int p[N];
int st[N][20],lg[N],d[N],id[N],tp[N>>1];
int f[N];
ll ans;
char s[N>>1],t[N];
struct node
{
	int fa,len;
	int ch[26];
}a[N];
void extend(int c,int x)
{
	int p=last,np=++tot;
	last=np;
	f[tot]=1;
	tp[x]=tot;
	a[np].len=a[p].len+1;
	while(p&&!a[p].ch[c]) a[p].ch[c]=np,p=a[p].fa;
	if(!p) a[np].fa=1;
	else 
	{
		int q=a[p].ch[c];
		if(a[q].len==a[p].len+1) a[np].fa=q;
		else 
		{
			int nq=++tot;
			a[nq]=a[q];
			a[nq].len=a[p].len+1;
			a[q].fa=a[np].fa=nq;
			while(q&&a[p].ch[c]==q) a[p].ch[c]=nq,p=a[p].fa;
		}
	}
}
void check(int l,int r)
{
	if(l<1||r>n||l>r) return ;
	int pos=tp[r];
	for(int i=lg[d[pos]];i;i--)
	{
		int x=st[pos][i];
		if(a[x].len>=r-l+1) pos=x;
	}
	ans=max(ans,1ll*f[pos]*(r-l+1));
}
void manacher()
{
	t[++m]='&';
	for(int i=1;i<=n;i++) t[++m]='#',t[++m]=s[i],id[m]=i;
	t[++m]='#';
	t[++m]='*';
	int mid=0,mr=0;
	for(int i=1;i<=m;i++)
	{
		if(i<mr) p[i]=min(p[2*mid-i],mr-i);
		else p[i]=1;
		check(id[i-p[i]+2],id[i+p[i]-2]);
		while(t[i+p[i]]==t[i-p[i]]) p[i]++,check(id[i-p[i]+2],id[i+p[i]-2]);
		if(p[i]+i>mr)
		{
			mr=p[i]+i;
			mid=i;
		}
	}
}
int c[N],b[N];
void build()
{
	for(int i=1;i<=n;i++) extend(s[i]-'a',i);
	for(int i=1;i<=tot;i++) c[a[i].len]++;
	for(int i=1;i<=n;i++) c[i]+=c[i-1];
	for(int i=1;i<=tot;i++) b[c[a[i].len]--]=i;
	for(int i=tot;i;i--) f[a[b[i]].fa]+=f[b[i]];
	for(int i=2;i<=tot;i++) lg[i]=lg[i>>1]+1;
	for(int i=1;i<=tot;i++)
	{
		int pos=b[i],fa=a[pos].fa;
		d[pos]=d[fa]+1;
		st[pos][0]=fa;
		for(int j=1;(1<<j)<=d[pos];j++) st[pos][j]=st[st[pos][j-1]][j-1];
	}	
	
}
int main ()
{
	scanf("%s",s+1);
	n=strlen(s+1);
	build();
	manacher();
	cout<<ans<<"\n";
	return 0;
}
posted @ 2024-03-28 18:13  zhouruoheng  阅读(11)  评论(0编辑  收藏  举报