【CF700E】Cool Slogans【后缀自动机】【可持久化线段树合并】【树上倍增】

传送门

题意:给定字符串 S S S,求一堆字符串 s 1 , s 2 , s 3 , . . . , s k s_1,s_2,s_3,...,s_k s1,s2,s3,...,sk,满足 s 1 s_1 s1 S S S的子串,且 s i s_i si s i − 1 s_{i-1} si1中至少出现两次,最大化 k k k

∣ S ∣ ≤ 200000 |S| \leq 200000 S200000

神仙题

显然建出后缀自动机然后在 f a i l fail fail树上乱搞

但很快发现不好描述出现两次

于是考虑回归到后缀自动机的本质: e n d p o s endpos endpos

对于子串 S , T S,T S,T,如果 S S S T T T中出现两次

那么在某一个 T T T的覆盖范围里, S S S出现了两次

换句话说,对于 T T T的任意一个 e n d p o s endpos endpos,记为 p o s [ T ] pos[T] pos[T],其覆盖范围为 [ p o s [ T ] − l e n [ T ] , p o s [ T ] ] [pos[T]-len[T],pos[T]] [pos[T]len[T],pos[T]]

这段里出现了两个 [ p o s [ S ] − l e n [ S ] , p o s [ S ] ] [pos[S]-len[S],pos[S]] [pos[S]len[S],pos[S]]

[ p o s [ T ] − l e n [ T ] + l e n [ S ] , p o s [ T ] ] [pos[T]-len[T]+len[S],pos[T]] [pos[T]len[T]+len[S],pos[T]]中有至少两个 S S S e n d p o s endpos endpos

于是可以用可持久化线段树来维护,由于某个点的 e n d p o s endpos endpos等于其 f a i l fail fail树的儿子的 e n d p o s endpos endpos的并集,写个线段树合并即可。

查询的时候由于是单调的,用倍增查最下面的满足条件的祖先。

复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#define MAXN 400005
using namespace std;
namespace SGT
{
	int ch[MAXN<<5][2],sum[MAXN<<5],cnt;
	inline int copy(const int& x)
	{
		int ans=++cnt;
		ch[ans][0]=ch[x][0],ch[ans][1]=ch[x][1],sum[ans]=sum[x];
		return ans;
	}
	void insert(int& x,int l,int r,int k)
	{
		++sum[x=++cnt];
		if (l==r) return;
		int mid=(l+r)>>1;
		if (k<=mid) insert(ch[x][0],l,mid,k);
		else insert(ch[x][1],mid+1,r,k);
	}
	int merge(int x,int y)
	{
		if (!x||!y) return x|y;
		int p=copy(x);
		sum[p]+=sum[y];
		ch[p][0]=merge(ch[p][0],ch[y][0]);
		ch[p][1]=merge(ch[p][1],ch[y][1]);
		return p;
	}
	int getpos(int x,int l,int r)
	{
		if (l==r) return l;
		int mid=(l+r)>>1;
		if (sum[ch[x][0]]) return getpos(ch[x][0],l,mid);
		else return getpos(ch[x][1],mid+1,r);
	}
	int query(int x,int l,int r,int ql,int qr)
	{
		if (ql<=l&&r<=qr) return sum[x];
		if (r<ql||qr<l) return 0;
		int mid=(l+r)>>1;
		return query(ch[x][0],l,mid,ql,qr)+query(ch[x][1],mid+1,r,ql,qr);
	}	
}
int n;
char s[MAXN];
namespace SAM
{
	int ch[MAXN][26],fa[MAXN],len[MAXN],pos[MAXN],tot=1,las=1;
	int rt[MAXN];
	void insert(int c)
	{
		int cur=++tot,p=las;
		len[cur]=len[p]+1;las=cur;
		for (;p&&!ch[p][c];p=fa[p]) ch[p][c]=cur;
		if (!p) return (void)(fa[cur]=1);
		int q=ch[p][c];
		if (len[q]==len[p]+1) fa[cur]=q;
		else
		{
			int _q=++tot;
			len[_q]=len[p]+1;
			fa[_q]=fa[q];fa[q]=fa[cur]=_q;
			memcpy(ch[_q],ch[q],sizeof(ch[q]));
			for(;ch[p][c]==q;p=fa[p]) ch[p][c]=_q;
		}
	}
	int jump[MAXN][20];
	int a[MAXN],c[MAXN],f[MAXN]={-1};
	inline bool check(int x,int y){return len[x]==0||SGT::query(rt[x],1,n,pos[y]-len[y]+len[x],pos[y])>=2;}
	int build()
	{
		for (int i=1;i<=n;i++) insert(s[i]-'a'),SGT::insert(rt[las],1,n,i);
		for (int i=1;i<=tot;i++) ++c[len[i]];
		for (int i=1;i<=n;i++) c[i]+=c[i-1];
		for (int i=tot;i>=1;i--) a[c[len[i]]--]=i;
		for (int i=tot;i>=1;i--)
		{
			pos[a[i]]=SGT::getpos(rt[a[i]],1,n);
			rt[fa[a[i]]]=SGT::merge(rt[fa[a[i]]],rt[a[i]]);
		}
		for (int i=1;i<=tot;i++)
		{
			jump[a[i]][0]=fa[a[i]];
			for (int k=1;k<20;k++) jump[a[i]][k]=jump[jump[a[i]][k-1]][k-1];
			int anc=a[i];
			for (int k=19;k>=0;k--) if (!check(jump[anc][k],a[i])) anc=jump[anc][k];
			anc=fa[anc];
			f[a[i]]=f[anc]+1;
		}
		int mx=0;
		for (int i=1;i<=tot;i++) mx=max(mx,f[i]);
		return mx;
	}	
}
int main()
{
	scanf("%d",&n);
	scanf("%s",s+1);
	printf("%d\n",SAM::build());
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值