【CF700E】Cool Slogans(SAM)(线段树合并)(DP)

传送门


洛谷上面那个翻译简直有毒,不知所云,最好自己去读英文题面。

题意简述如下:

给你一个字符串 S S S,请你构造一个字符串序列 s 1 , s 2 ⋯   , s k s_1,s_2\cdots ,s_k s1,s2,sk,使得 ∀ i , s i \forall i,s_i i,si S S S的子串,且 s i s_i si s i − 1 s_{i-1} si1中出现了至少两次,请问你最大化 k k k,即序列长度。

题解:

我们只考虑 s i s_i si s i − 1 s_{i-1} si1的后缀情况,否则我们可以把 s i − 1 s_{i-1} si1砍到 s i s_i si是它的一个后缀。

那么直接建出SAM,所有祖先显然都在儿子中出现了至少一次。

我们直接询问right集合可以知道祖先是否在儿子中出现了至少两次。

保留之前的答案和得到这个答案的最短的祖先转移即可。

由于需要询问right集合,用线段树合并随便搞一下即可。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

using std::cerr;
using std::cout;

cs int N=2e5+7;

char s[N];int n;

namespace SGT{
	cs int N=::N*40;
	int lc[N],rc[N],tot;
	inline int _copy(int u){++tot;lc[tot]=lc[u],rc[tot]=rc[u];return tot;}
	inline void ins(int &u,int l,int r,int p){
		u=++tot;if(l==r)return ;int mid=l+r>>1;
		(p<=mid)?ins(lc[u],l,mid,p):ins(rc[u],mid+1,r,p); 
	}
	inline void merge(int &u,int v){
		if(!u||!v){u|=v;return ;}u=_copy(u);
		merge(lc[u],lc[v]);
		merge(rc[u],rc[v]);
	}
	inline bool query(int u,int l,int r,int ql,int qr){
		if(!u)return false;if(ql<=l&&r<=qr)return true;
		int mid=l+r>>1;
		if(qr<=mid)return query(lc[u],l,mid,ql,qr);
		if(mid<ql)return query(rc[u],mid+1,r,ql,qr);
		return query(lc[u],l,mid,ql,qr)||query(rc[u],mid+1,r,ql,qr);
	}
}

namespace SAM{
	cs int N=::N<<1|1;
	int son[N][26],fa[N],len[N],ps[N],now;
	inline void push_back(int i,int c){
		int cur=i,p=(i-1)?(i-1):(n+1);
		len[cur]=len[p]+1;ps[cur]=i;
		for(;p&&!son[p][c];p=fa[p])son[p][c]=cur;
		if(!p)fa[cur]=n+1;
		else if(len[son[p][c]]==len[p]+1)fa[cur]=son[p][c];
		else {
			int nq=++now,q=son[p][c];
			memcpy(son[nq],son[q],sizeof son[q]);
			len[nq]=len[p]+1;ps[nq]=ps[q];
			fa[nq]=fa[q];fa[q]=fa[cur]=nq;
			for(;p&&son[p][c]==q;p=fa[p])son[p][c]=nq;
		}
	}
	int bin[N],nd[N],rt[N];
	inline void build(){
		for(int re i=1;i<=now;++i)++bin[len[i]];
		for(int re i=1;i<=n;++i)bin[i]+=bin[i-1];
		for(int re i=1;i<=now;++i)nd[bin[len[i]]--]=i;
		for(int re i=1;i<=n;++i)SGT::ins(rt[i],1,n,i);
		for(int re i=now;i;--i)SGT::merge(rt[fa[nd[i]]],rt[nd[i]]);
	}
	int ans[N],tp[N];
	inline void solve(){
		for(int re i=2;i<=now;++i){
			int u=nd[i],f=fa[u];
			if(f==n+1){ans[u]=1,tp[u]=u;continue;}
			if(SGT::query(rt[tp[f]],1,n,ps[u]-len[u]+len[tp[f]],ps[u]-1)){
				ans[u]=ans[f]+1,tp[u]=u;
			}else ans[u]=ans[f],tp[u]=tp[f];
		}
		cout<<*std::max_element(ans+1,ans+now+1)<<"\n"; 
	}
}

signed main(){
#ifdef zxyoi
	freopen("slogan.in","r",stdin);
#endif
	scanf("%d%s",&n,s+1);SAM::now=n+1;
	for(int re i=1;i<=n;++i)SAM::push_back(i,s[i]-'a');
	SAM::build();SAM::solve();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值