题解 CF700E Cool Slogans

题解 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(n\log^2 n)\)。可以用 two pointers 代替二分做到更优的复杂度,不过没必要。

参考代码:

//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;
}
posted @ 2020-02-29 16:15  duyiblue  阅读(199)  评论(0编辑  收藏  举报