【CF700E】Cool Slogans

题目

题目链接:https://codeforces.com/problemset/problem/700/E
给定一个字符串 \(S\),要求构造字符串序列 \(s_1,s_2,\ldots,s_k\),满足任意 \(s_i\) 都是 \(S\) 的子串,且任意 \(i\in[2,n]\),都有 \(s_{i-1}\)\(s_i\) 中出现了至少 \(2\) 次(可以有重叠部分,只要起始、结尾位置不同即可)。
求可能的最大的 \(k\) 的值(即序列的最大可能长度)。
\(n\leq 2\times 10^5\)

思路

首先有一个比较显然的结论,最终序列任意 \(s_i,s{i+1}\) 都应该满足 \(s_i\)\(s_{i+1}\) 的后缀。因为如果不是,那么删掉 \(s_{i+1}\) 后面若干个字符直到 \(s_i\) 是其后缀显然不劣。
那么对于 parent 树上的两个节点 \(x,y\),假设 \(x\)\(y\) 的祖先,那么如果 \(x\) 所表示的等价类中长度最长的串在 \(y\) 长度最长的串中出现了至少两次,那么就可以从 \(x\) 转移到 \(y\)
由于 \(x\) 中任意一个字符串必然是 \(y\) 的后缀,所以对于 \(y\)\(\mathrm{endpos}\) 集合中任意一个元素 \(\mathrm{pos}_y\),如果存在一个 \(\mathrm{pos}_x\) 满足 \(\mathrm{pos}_x\in[\mathrm{pos}_y-\mathrm{len}_y+\mathrm{len}_x,\mathrm{pos}_y)\),那么 \(x\) 就至少在 \(y\) 的任意串中出现了至少两次。
所以我们先把 SAM 建好,用可持久化线段树合并求出每一个节点的 \(\mathrm{endpos}\),然后在 parent 树上 dp 即可。
注意任意节点不一定只能从其父亲转移而来,所以需要记录一个 \(g_x\) 表示节点 \(x\) 是被哪一个节点转移到的。
时间复杂度 \(O(n\log n)\)

代码

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

const int N=400010,LG=20;
int n,tot,ans,head[N],rt[N],a[N],b[N],f[N],g[N];
char s[N];

struct edge
{
	int next,to;
}e[N];

void add(int from,int to)
{
	e[++tot]=(edge){head[from],to};
	head[from]=tot;
}

struct SegTree
{
	int tot,lc[N*LG*4],rc[N*LG*4];
	bool flag[N*LG*4];
	
	int ins(int l,int r,int k)
	{
		int x=++tot;
		flag[x]=1;
		if (l==r) return x;
		int mid=(l+r)>>1;
		if (k<=mid) lc[x]=ins(l,mid,k);
		if (k>mid) rc[x]=ins(mid+1,r,k);
		return x;
	}
	
	int merge(int x,int y)
	{
		if (!x || !y) return x|y;
		int p=++tot;
		flag[p]=flag[y]|flag[x];
		lc[p]=merge(lc[x],lc[y]);
		rc[p]=merge(rc[x],rc[y]);
		return p;
	}
	
	int query(int x,int l,int r,int ql,int qr)
	{
		if (ql<=l && qr>=r) return flag[x];
		int mid=(l+r)>>1,s=0;
		if (ql<=mid) s|=query(lc[x],l,mid,ql,qr);
		if (qr>mid) s|=query(rc[x],mid+1,r,ql,qr);
		return s;
	}
}seg;

struct SAM
{
	int tot,last,len[N],pos[N],fa[N],ch[N][26];
	SAM() { tot=last=1; }
	
	void ins(int c,int id)
	{
		int p=last,np=++tot;
		last=np; len[np]=len[p]+1; pos[np]=id;
		for (;p && !ch[p][c];p=fa[p]) ch[p][c]=np;
		if (!p) fa[np]=1;
		else
		{
			int q=ch[p][c];
			if (len[q]==len[p]+1) fa[np]=q;
			else
			{
				int nq=++tot;
				len[nq]=len[p]+1; fa[nq]=fa[q]; pos[nq]=pos[q];
				for (int i=0;i<26;i++) ch[nq][i]=ch[q][i];
				fa[np]=fa[q]=nq;
				for (;p && ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
			}
		}
	}
	
	void buildseg()
	{
		for (int i=2;i<=tot;i++)
			rt[i]=seg.ins(1,n,pos[i]);
		for (int i=1;i<=tot;i++) b[len[i]]++;
		for (int i=1;i<=tot;i++) b[i]+=b[i-1];
		for (int i=1;i<=tot;i++) a[b[len[i]]--]=i;
		for (int i=tot;i>=1;i--)
		{
			int j=a[i];
			if (fa[j]!=1)
				rt[fa[j]]=seg.merge(rt[fa[j]],rt[j]);
			add(fa[j],j);
		}
	}
	
	void dfs(int x)
	{
		for (int i=head[x];~i;i=e[i].next)
		{
			int v=e[i].to;
			if (x!=1 && seg.query(rt[g[x]],1,n,pos[v]-len[v]+len[g[x]],pos[v]-1))
				f[v]=f[g[x]]+1,g[v]=v;
			else if (x!=1)
				f[v]=f[g[x]],g[v]=g[x];
			else
				f[v]=1,g[v]=v;
			ans=max(ans,f[v]);
			dfs(v);
		}
	}
}sam;

int main()
{
	memset(head,-1,sizeof(head));
	scanf("%d%s",&n,s+1);
	for (int i=1;i<=n;i++)
		sam.ins(s[i]-'a',i);
	sam.buildseg();
	sam.dfs(1);
	printf("%d",ans);
	return 0;
}
posted @ 2021-01-13 15:01  stoorz  阅读(106)  评论(0编辑  收藏  举报