CF700E Cool Slogans

27 篇文章 0 订阅
10 篇文章 0 订阅
该博客主要探讨了一种字符串处理问题,即如何构建一系列子串,使得每个子串都是原字符串的子串,并在前一个子串中至少出现两次。博主通过证明在后缀树上进行动态规划的有效性,并利用可持久化线段树合并endpos信息,实现了在$O(n)$复杂度内求解最大子串数量的方法。文章详细解释了算法思路并给出了C++代码实现。
摘要由CSDN通过智能技术生成

CF700E Cool Slogans

  • 给定一个字符串 S S S,要求构造字符串序列 s 1 , s 2 , … , s k s_1,s_2,…,s_k s1,s2,,sk满足任意 s i s_i si 都是 S S S的子串,且 s i s_i si s i − 1 s_{i-1} si1中出现至少两次。
  • 求最大 k k k
  • ∣ S ∣ ≤ 2 e 5 |S|\le2e5 S2e5

Solution

  • 首先所有的 s i s_i si的序列都可以缩成 s i s_i si s i − 1 s_{i-1} si1后缀的形式,所以考虑直接在后缀树上进行DP。
  • 需要证明一个重要的结论:对于SAM上的节点 x x x,只有它的 m a x l e n maxlen maxlen是可能作为转移状态的。
    • 只需要证明对于 x x x的祖先节点 y y y,任意 y y y [ m i n l e n , m a x l e n ] [minlen,maxlen] [minlen,maxlen]中的字符串在 x x x代表的最长字符串中出现次数都相同。
    • 考虑如果 a , b a,b a,b都是 y y y代表的字符串, ∣ b ∣ = ∣ a ∣ + 1 |b|=|a|+1 b=a+1 b b b a a a少出现一次,多出来的字符一定在 s t r m a x x str_{max}x strmaxx的外面,那么由于已经是最长的了,所以这个长度为 ∣ s t r m a x x ∣ + 1 |str_{max}x|+1 strmaxx+1的串属于 z z z z z z x x x子树内节点,那么存在一个 e n d p o s = e n d p o s ( z ) − e n d p o s ( x ) endpos=endpos(z)-endpos(x) endpos=endpos(z)endpos(x)使得存在 b b b而不存在 a a a,与 a , b a,b a,b相同 e n d p o s endpos endpos矛盾。
  • 那么接下来就直接可持久化线段树合并 e n d p o s endpos endpos,再根据单调性将转移到当前节点 x x x y y y贪心地移动即可。判断只需要在随便选择一个 e n d p o s ( x ) endpos(x) endpos(x),判断这个区间内的情况就好了。
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define maxn 400005
#define maxm 10000005
using namespace std;

int n,i,j,k;
struct node{
	int to[26],len,end,fa;
} T[maxn];
int tot,las,Rt;
void add(int c,int end){
	int np=++tot,p=las; las=np;
	T[np].len=T[p].len+1,T[np].end=end;
	for(;p&&!T[p].to[c];p=T[p].fa) T[p].to[c]=np;
	if (!p) T[np].fa=Rt; else {
		int q=T[p].to[c];
		if (T[q].len==T[p].len+1) T[np].fa=q; else {
			int nq=++tot; T[nq]=T[q],T[nq].end=0;
			T[nq].len=T[p].len+1,T[q].fa=T[np].fa=nq;
			for(;p&&T[p].to[c]==q;p=T[p].fa) T[p].to[c]=nq;
		}
	}
}

int em,e[maxn],nx[maxn],ls[maxn];
void insert(int x,int y){
	em++; e[em]=y; nx[em]=ls[x]; ls[x]=em;
}

int t[maxm],tl[maxm],tr[maxm],cnt,rt[maxn];
void change(int &x,int l,int r,int p){
	if (!x) x=++cnt; t[x]++;
	if (l==r) return;
	int mid=(l+r)>>1;
	if (p<=mid) change(tl[x],l,mid,p);
	else change(tr[x],mid+1,r,p);
}

int find(int x,int l,int r,int L,int R){
	if (!x||l>R||r<L) return 0;
	if (L<=l&&r<=R) return t[x];
	int mid=(l+r)>>1;
	return find(tl[x],l,mid,L,R)+find(tr[x],mid+1,r,L,R);
}

void merge(int &x,int y,int l,int r){
	t[++cnt]=t[x]+t[y],tl[cnt]=tl[x],tr[cnt]=tr[x],x=cnt;
	if (l==r) return;
	int mid=(l+r)>>1;
	if (tl[x]&&tl[y]) merge(tl[x],tl[y],l,mid);
	else if (tl[y]) tl[x]=tl[y];
	if (tr[x]&&tr[y]) merge(tr[x],tr[y],mid+1,r);
	else if (tr[y]) tr[x]=tr[y];
}

void cover(int x){
	if (tl[x]) cover(tl[x]);
	if (tr[x]) cover(tr[x]);
}

void dfs(int x){
	rt[x]=++cnt;
	if (T[x].end) change(rt[x],1,n,T[x].end);
	for(int i=ls[x];i;i=nx[i]) {
		dfs(e[i]);
		merge(rt[x],rt[e[i]],1,n);
		T[x].end=T[e[i]].end;
	}
}

int d[maxn],f[maxn],ans;
int check(int y,int x){
	return find(rt[y],1,n,T[x].end-T[x].len+T[y].len,T[x].end-1)>0;
}

void dp(int x,int y){
	while (y<d[0]&&check(d[y+1],x)) y++;
	f[x]=f[d[y]]+1;
	ans=max(ans,f[x]);
	d[++d[0]]=x;
	for(int i=ls[x];i;i=nx[i]) dp(e[i],y);
	d[0]--;
}

int main(){
	freopen("ceshi.in","r",stdin);
	scanf("%d",&n);
	char ch=getchar(); while (ch<'a'||ch>'z') ch=getchar();
	tot=Rt=las=1;
	for(i=1;i<=n;i++) add(ch-'a',i),ch=getchar();
	for(i=1;i<=tot;i++) if (T[i].fa) 
		insert(T[i].fa,i);
	dfs(Rt);
	for(i=ls[Rt];i;i=nx[i]) {
		dp(e[i],0);
	}
	printf("%d",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值