【CF1063F】String Journey

题目

题目链接:https://codeforces.com/problemset/problem/1063/F
对于一个字符串数组 \(t_1\),....,\(t_k\),若对于每一个 \(t_i\) 都是 \(t_i-1\) 的真子串的话,则称为有序串组,列如 \(\{ab,b\}\) 是,\(\{ab,c\}\)\(\{a,a\}\) 不是。
给定字符串 \(s\),构造有序串组 \(t_1,...,t_k\) 和任意字符串数组 \(u_1,...,u_{k+1}\),使 \(s=u_1+t_1+u_2+t_2...+t_k+u_{k+1}\),其中 \(+\) 为字符串的拼接。
现在给定一个字符串,求满足条件的最大 \(k\)
\(n\leq 5\times 10^5\)

思路

\(f_i\) 表示以 \(i\) 开头的后缀选出有序串组的最大数量(必须选 \(i\))。
首先有几个相对明显的性质:

  1. 对于一个有序串组 \(t_1,t_2,\cdots t_k\),任意相邻的两项的长度一定差 \(1\)
    显然吧,不然删掉多余的字符不会更劣。

  2. 对于 \(s\) 一个以 \(i\) 开头的后缀,如果可以选出长度为 \(k\) 的有序串组,那么一定可以选出长度为 \(k-1\) 的有序串组。
    因为我们把这 \(k\) 字符串的最后一个字符都删去后显然得到了一个长度为 \(k-1\) 的有序串组。

  3. \(f_{i+1}\geq f_{i}-1\)
    我们可以把从 \(i\) 开始的最长有序串组中每一个字符串的第一个字符删去,就可以得到一个从 \(i+1\) 开始,长度为 \(f_{i}-1\) 的有序串组。

由性质 3,我们可以得到 \(f_i\leq f_{i+1}+1\)
这也就意味着 \(\sum^{n-1}_{i=1}|f_i-f_{i+1}|\)\(O(n)\) 级别的。假设我们一定确定了 \(f_{i+1}\sim f_{n}\),我们可以通过从 \(f_{i+1}+1\) 倒序枚举到 \(1\),并判断 \(f_i\) 是否可以为当前枚举的这一个值。根据性质 2,\(f_i\) 越大肯定越优。
那么现在就转化成了一个判定性问题了。
假设枚举到 \(res\),考虑如何判断 \(f_i\) 能否为 \(res\)。根据性质 1,如果 \(f_i\) 可以为 \(res\),那么必然存在一个 \(j\),满足:

  • \(j\geq i+f_i\)
  • \(f_j\geq f_i-1\)
  • \(s[i:i+f_i-2]\) 是字符串 \(s\) 中以 \(j\) 开头的后缀的前缀,或 \(s[i+1:i+f_i-1]\) 是字符串 \(s\) 中以 \(j\) 开头的后缀的前缀。

看到一个后缀的前缀,我们不难想到建出反串的 SAM,那么在原串中,如果 \(s[l:r]\) 是一个后缀 \(s[k:n]\) 的前缀,在反串 SAM 上 \(s[l:r]\) 所在等价类一定是 \(s[k:n]\) 所在等价类在 parent 树上的祖先。
给 parent 树的点按照 dfs 序编号后(记 \(pos_i\) 表示以 \(i\) 开头的后缀所在等价类在 parent 树上的 dfs 序,\(id[i]\) 表示 SAM 上点 \(i\) 的 dfs 序),我们的操作转化为:

  • \(pos_j\) 插入一个点 \((j,f_j)\)
  • 询问 \(\left [id[k],id[k]+siz[k]\right )\) 的区间内是否存在二元组 \((a,b)\) 满足 \(a\geq i+f_i\),且 \(b\geq f_{i}-1\)

这是一个二维偏序。但是我们发现随着 \(i\) 的减小,\(i+f_i\) 是单调不增的,所以可以直接指针维护一下第一维,第二维用线段树即可。
时间复杂度 \(O(n\log n)\)

代码

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

const int N=1000010,LG=20;
int n,m,ans,tot,f[N],pos[N],head[N],siz[N],id[N],pa[N][LG+1];
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 SAM
{
	int last,tot,ch[N][26],fa[N],len[N];
	SAM() { last=tot=1; len[0]=-1; }
	
	void ins(int c,int j)
	{
		int p=last,np=++tot; 
		len[np]=len[p]+1; last=pos[j]=np;
		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;
				fa[nq]=fa[q]; len[nq]=len[p]+1;
				memcpy(ch[nq],ch[q],sizeof(ch[nq]));
				fa[q]=fa[np]=nq;
				for (;p && ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
			}
		}
	}
}sam;

struct SegTree
{
	int maxn[N*4];
	
	void update(int x,int l,int r,int k,int v)
	{
		maxn[x]=max(maxn[x],v);
		if (l==r) return;
		int mid=(l+r)>>1;
		if (k<=mid) update(x*2,l,mid,k,v);
			else update(x*2+1,mid+1,r,k,v);
	}
	
	int query(int x,int l,int r,int ql,int qr)
	{
		if (ql<=l && qr>=r) return maxn[x];
		int mid=(l+r)>>1,res=0;
		if (ql<=mid) res=max(res,query(x*2,l,mid,ql,qr));
		if (qr>mid) res=max(res,query(x*2+1,mid+1,r,ql,qr));
		return res;
	}
}seg;

void build()
{
	m=sam.tot;
	memset(head,-1,sizeof(head));
	for (int i=2;i<=m;i++) add(sam.fa[i],i);
}

void dfs(int x)
{
	pa[x][0]=sam.fa[x]; id[x]=++tot; siz[x]=1;
	for (int i=1;i<=LG;i++)
		pa[x][i]=pa[pa[x][i-1]][i-1];
	for (int i=head[x];~i;i=e[i].next)
	{
		int v=e[i].to;
		dfs(v); siz[x]+=siz[v];
	}
}

int find(int x,int k)
{
	for (int i=LG;i>=0;i--)
		if (sam.len[pa[x][i]]>=k) x=pa[x][i];
	return x; 
}

bool check(int i)
{
	int x=find(pos[i],f[i]-1),y=find(pos[i+1],f[i]-1);
	if (seg.query(1,1,m,id[x],id[x]+siz[x]-1)>=f[i]-1) return 1;
	if (seg.query(1,1,m,id[y],id[y]+siz[y]-1)>=f[i]-1) return 1;
	return 0;
}

int main()
{
	scanf("%d%s",&n,s+1);
	for (int i=n;i>=1;i--)
		sam.ins(s[i]-'a',i);
	build(); tot=0; dfs(1);
	for (int i=n;i>=1;i--)
	{
		f[i]=f[i+1]+1;
		for (;!check(i);f[i]--)
			seg.update(1,1,m,id[pos[i+f[i]-1]],f[i+f[i]-1]);
		ans=max(ans,f[i]);
	}
	cout<<ans;
	return 0;
}
posted @ 2021-06-15 12:03  stoorz  阅读(39)  评论(0编辑  收藏  举报