音乐播放器
HMF-have much fun Site
 
文章 标签
16

Powered by Gridea | Theme: Fog
载入天数...
载入时分秒...
总访问量:  |   访问人数:

【题解】CF700E Cool Slogans

  热度: loading...

串串题!

个人感觉本题和CF1063F 很像,特别是关于相邻串之间的条件,可以先去看看这题。

考虑在同样满足条件的情况下,si1s_{i-1}sis_i 的后缀的情况一定比 si1s_{i-1} 不是 sis_i 后缀的情况优。

证明:

如果 si1s_{i-1} 不是 sis_i 的后缀,那么我们把 sis_i 中, si1s_{i-1} 第二次出现位置的结尾之后的字符全部删掉,得到 sis_i' 。则 si1s_{i-1}sis_i' 依旧满足条件,且由于 sis_i'sis_i 的子串,sis_i'si+1s_{i+1} 依旧满足条件,对整个序列的合法性不造成影响。

因此我们只考虑 si1s_{i-1}sis_i 后缀的情况。

考虑建立后缀自动机。对于后缀自动机的 parent tree 而言,每个点 iifaifa_i 所代表的最长串都是点 ii 所代表的最长串的严格后缀。我们先只考虑从 faifa_iii 贡献。既然 faifa_i 作为 ii 的后缀,那它第二次出现的位置已经确定了,只需要考虑它是否有在前面的位置出现过即可。设点 ii 所代表的最长串的第一次出现位置为 pip_i ,计算 faifa_iendposendpos 集合是否在 [pileni+lenfai,pi1][p_i-len_i+len_{fa_i},p_i-1] 中有元素即可。

我们考虑用线段树合并维护每个点的 endposendpos 集合。考虑一个点的 endposendpos 集合由自己的所有儿子的 endposendpos 集合合并而来,且当且仅当 该点所代表的最长串是整个串的一个前缀时 会缺少最长串第一次出现的位置,故从儿子合并上来后加入最长串第一次出现的位置即可。

现在来证明一个结论:对于一个 fif_i 而言,当 fif_i 的最长串符合条件时,fif_i 中的所有串符合条件。当 fif_i 的最长串不符合条件时,fif_i 中的所有串都不符合条件。

证明:

首先第一点很好证,由于 fif_i 中的所有其它串都是 fif_i 的最长串的后缀,因此只要 fif_i 的最长串符合条件其也一定符合条件。

对于第二点,我们假设 fif_i 的最长串不在 ii 的最长串中出现两次,而 fif_i 所代表的另一个更小的串在 ii 的最长串中出现了两次。由于两个串的 endposendpos 集合相同,因此必然可以在 ii 的最长串的前面添加一串字符得到字符串 ww ,使得 fif_i 所代表的的最长串在 ww 中出现两次。由于 ii 的最长串是 ww 的后缀,因此 ww 属于 ii 的儿子。但是由于每个 ii 的最长串都必定为一个 ww 的后缀,所以 endposiendposwendpos_i \in endpos_w ,这点证明 ww 属于 ii 自身或 ii 的祖先,与之前推导出的 ww 属于 ii 的儿子矛盾,假设不成立。

fif_i 为考虑到 ii 时序列的最长长度,gig_i 为考虑到 ii 时序列结尾的位置。如果 ii 的最长串与 gfaig_{fa_i} 满足条件,则 fi=ffai+1f_i = f_{fa_i}+1 ,且 gi=ig_i=i ,否则 fi=ffaif_i=f_{fa_i}gi=gfaig_i=g_{fa_i} 。将后缀自动机的节点拓扑排序一下从后往前 dpdp 即可。

CodeCode

#include<bits/stdc++.h>
#define N 600010
#define mid (l+r)/2
using namespace std;
int n;
char s[N];
int ls[N<<5],rs[N<<5],cnt,rt[N];
void update(int &k,int l,int r,int pos) {
	if(!k) k=++cnt;
	if(l==r) return ;
	if(pos<=mid) update(ls[k],l,mid,pos);
	else update(rs[k],mid+1,r,pos);
}
int merge(int x,int y,int l,int r) {
	if(!x||!y) return x+y;
	int now=++cnt;
	if(l!=r) {
		ls[now]=merge(ls[x],ls[y],l,mid);
		rs[now]=merge(rs[x],rs[y],mid+1,r);
	}
	return now;
}
int query(int k,int l,int r,int L,int R) {
    if(!k||l>R||r<L) return 0;
    if(L<=l&&r<=R) return 1;
    if(R<=mid) return query(ls[k],l,mid,L,R);
    else if(L>mid) return query(rs[k],mid+1,r,L,R);
    else return query(ls[k],l,mid,L,R)||query(rs[k],mid+1,r,L,R);
}

struct SAM {
	int tr[N][30],fa[N],lst,tot,pos[N],len[N];
	SAM(){lst=tot=1;}
 	void add(int c,int w) {
		int p=lst,np=++tot;
		len[np]=len[p]+1,pos[np]=w,lst=np;
		update(rt[np],1,n,w);
		while(p&&!tr[p][c]) tr[p][c]=np,p=fa[p];
		if(!p) {fa[np]=1;return ;}
		int q=tr[p][c];
		if(len[p]+1==len[q]) {fa[np]=q;return ;}
		int nq=++tot;
		len[nq]=len[p]+1;
		fa[nq]=fa[q],pos[nq]=pos[q],fa[q]=fa[np]=nq;
		for(int i=0; i<26; i++) tr[nq][i]=tr[q][i];
		while(p&&tr[p][c]==q) tr[p][c]=nq,p=fa[p];
	}
	int buc[N],rk[N];
	void topo() {
		for(int i=1; i<=tot; i++) buc[len[i]]++;
		for(int i=1; i<=n; i++) buc[i]+=buc[i-1];
		for(int i=1; i<=tot; i++) rk[buc[len[i]]--]=i; 
	}
}sam;
int read() {
	int res=0,f=1;char ch=getchar();
	while(!isdigit(ch)) f=ch=='-'?-1:1,ch=getchar();
	while(isdigit(ch)) res=res*10+ch-'0',ch=getchar();
	return f*res;
}
int f[N],g[N],ans;
int main()
{
	n=read();
	scanf("%s",s+1);
	for(int i=1; i<=n; i++) sam.add(s[i]-'a',i);
	sam.topo();
	for(int i=sam.tot; i>1; i--) {
		int u=sam.rk[i],v=sam.fa[u];
		rt[v]=merge(rt[v],rt[u],1,n);
	}
	for(int i=2; i<=sam.tot; i++) {
		int u=sam.rk[i],v=sam.fa[u];
		if(v==1) f[u]=1,g[u]=u;
		else if(query(rt[g[v]],1,n,sam.pos[u]-sam.len[u]+sam.len[g[v]],sam.pos[u]-1)) f[u]=f[v]+1,g[u]=u;
		else f[u]=f[v],g[u]=g[v];
		ans=max(ans,f[u]);
	}
	printf("%d",ans);
	return 0;
}