【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)
(nleq 5 imes 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_ileq 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),满足:

  • (jgeq i+f_i)
  • (f_jgeq 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] ight )) 的区间内是否存在二元组 ((a,b)) 满足 (ageq i+f_i),且 (bgeq f_{i}-1)

这是一个二维偏序。但是我们发现随着 (i) 的减小,(i+f_i) 是单调不增的,所以可以直接指针维护一下第一维,第二维用线段树即可。
时间复杂度 (O(nlog 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;
}
原文地址:https://www.cnblogs.com/stoorz/p/14884951.html