题解 CF700E Cool Slogans

建出SAM及parent树。根据大家所熟知的套路,我们可以用线段树合并来维护enspos集合,这样就可以(O(log n))查询线段树某个节点上的子串在原串的某个区间([l,r])内出现了几次。具体做法请见 NOI2018你的名字。

引理:

对于parent树上任意一个节点(x),以及它的子树内任意一个节点(y),设节点(y)代表的最长串为(S),设节点(x)代表的串为(T_1,T_2,T_3,dots,T_k),设(F(S,T))表示串(T)(S)中的出现次数,则

[F(S,T_1) = F(S,T_2) = F(S,T_3) =dots=F(S,T_k) ]

证明:

考虑如果存在一个(|T_a|+1=|T_b|),且(T_a)(T_b)(S)内的出现次数不同。

此时要么是存在一个能匹配(T_a)的位置,它的左边不匹配(T_b),那就说明(T_a)在原串里的出现次数比(T_b)多,与“(T_a,T_b)在SAM的同一个节点上”矛盾。

要么是在(T_a)(S)的一个前缀,而(T_b)因为太长被卡了一下(如下图)。

如果要使(T_a,T_b)出现次数一样,那么虽然(S)的前面到此为止了((T_b)被卡了一下),但真正的原串里在这个位置(T_b)肯定没被卡。所以(S)必定存在一个儿子,我们称这个串为(S')。它满足:(|S'|=|S|+1),且(T_a)在这个(S')中出现次数和(T_b)一样(即(T_b)没有被卡)。

又因为(S')(S)不在同一个节点上(因为(S)是它所在节点的最长串),所以(S')在原串里的出现次数必定少于(S)。当(S')出现时,(T_a,T_b)得到的出现次数相同;而在某个(或某些)(S)出现了,(S')却没有出现的位置,(T_a)就会比(T_b)多匹配(1)次。这还是与“(T_a,T_b)在SAM的同一个节点上”矛盾。

故原命题得证。

有了这个结论,我们就可考虑在parent树上DP。设(dp[u])表示以(u)这个节点所代表的的最长串,作为挑选出来的答案集合中最后一个串时,答案集合中最多能有多少串。

(dp[u])肯定是从(u)的一个子串转移。考虑如果从(u)的后缀转移,答案肯定不会变劣,因为这个转移点“子串”在(u)中最后一次出现的位置之后的部分,是无用的,把这部分砍掉答案不会变劣。甚至还可以发现,答案集合中每个串一定是下一个串的border。于是我们直接从parent树上的祖先转移到(dp[u])即可。对每个(u),在它的祖先中二分出一个深度最大的,且在(u)中出现了至少(2)次的节点(v),直接令(dp[u]=dp[v]+1)即可。

答案就是(max_{i=1}^{n}{dp[i]})

时间复杂度(O(nlog n))

参考代码:

//problem:CF700E
#include <bits/stdc++.h>
using namespace std;
 
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second
 
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
 
namespace Fread{
const int MAXN=1<<20;
char buf[MAXN],*S,*T;
inline char getchar(){
	if(S==T){
		T=(S=buf)+fread(buf,1,MAXN,stdin);
		if(S==T)return EOF;
	}
	return *S++;
}
}//namespace Fread
#ifdef ONLINE_JUDGE
	#define getchar Fread::getchar
#endif
inline int read(){
	int f=1,x=0;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
inline ll readll(){
	ll f=1,x=0;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
/*  ------  by:duyi  ------  */ // dysyn1314
const int MAXN=200000;
char s[MAXN+5];
struct SegmentTree{
	int sum[MAXN*40],ls[MAXN*40],rs[MAXN*40],cnt;
	void ins(int &p,int l,int r,int pos){
		if(!p)p=++cnt;sum[p]++;
		if(l==r)return;
		int mid=(l+r)>>1;
		if(pos<=mid)ins(ls[p],l,mid,pos);
		else ins(rs[p],mid+1,r,pos);
	}
	int merge(int x,int y){
		if(!x||!y) return x+y;
		int z=++cnt;
		sum[z]=sum[x]+sum[y];
		ls[z]=merge(ls[x],ls[y]);
		rs[z]=merge(rs[x],rs[y]);
		return z;
	}
	int query(int p,int tl,int tr,int ql,int qr){
		if(!p) return 0;
		if(ql<=tl && qr>=tr) return sum[p];
		int mid=(tl+tr)>>1,res=0;
		if(ql<=mid)res+=query(ls[p],tl,mid,ql,qr);
		if(qr>mid)res+=query(rs[p],mid+1,tr,ql,qr);
		return res;
	}
	SegmentTree(){}
}T;
 
int n,cnt,ed,fa[MAXN*2+5],mp[MAXN*2+5][26],len[MAXN*2+5],pos[MAXN*2+5],rt[MAXN*2+5];
void ins(int c){
	int p=ed;ed=++cnt;len[ed]=len[p]+1;pos[ed]=len[ed];T.ins(rt[ed],1,n,pos[ed]);
	for(;p&&!mp[p][c];p=fa[p])mp[p][c]=ed;
	if(!p){fa[ed]=1;return;}
	int q=mp[p][c];
	if(len[q]==len[p]+1){fa[ed]=q;return;}
	len[++cnt]=len[p]+1;pos[cnt]=pos[ed];
	for(int i=0;i<26;++i)mp[cnt][i]=mp[q][i];
	fa[cnt]=fa[q];fa[q]=fa[ed]=cnt;
	for(;mp[p][c]==q;p=fa[p])mp[p][c]=cnt;
}
struct EDGE{int nxt,to;}edge[MAXN*2+5];
int head[MAXN*2+5],tot;
inline void add_edge(int u,int v){edge[++tot].nxt=head[u],edge[tot].to=v,head[u]=tot;}
void dfs1(int u){
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].to;
		dfs1(v);
		rt[u]=T.merge(rt[u],rt[v]);
	}
}
int sta[MAXN*2+5],top,dp[MAXN*2+5],ans;
int ask(int small,int big){
	//small在big里出现了多少次
	int l=pos[big]-len[big]+1,r=pos[big];
	if(r-l+1<len[small])return 0;
	return T.query(rt[small],1,n,l+len[small]-1,r);
}
void dfs2(int u){
	if(u!=1)dp[u]=1;
	if(top){
//		for(int i=top;i>=1;--i){
//			if(i<top)assert(dp[sta[i]]<=dp[sta[i+1]]);
//			if(ask(sta[i],u)>=2){
//				dp[u]=max(dp[u],dp[sta[i]]+1);
//			}
//		}
		int l=1,r=top;
		while(l<r){
			int mid=(l+r+1)>>1;
			if(ask(sta[mid],u)>=2)l=mid;
			else r=mid-1;
		}
		if(ask(sta[l],u)>=2)dp[u]=dp[sta[l]]+1;
	}
	ans=max(ans,dp[u]);
	if(u!=1)sta[++top]=u;
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].to;
		dfs2(v);
	}
	if(u!=1)--top;
}
int main() {
	cnt=ed=1;
	scanf("%d%s",&n,s+1);
	for(int i=1;i<=n;++i)ins(s[i]-'a');
	for(int i=2;i<=cnt;++i)add_edge(fa[i],i);
	dfs1(1);dfs2(1);cout<<ans<<endl;
	return 0;
}
原文地址:https://www.cnblogs.com/dysyn1314/p/12383459.html