CF700E Cool Slogans(SAM,dp)

本文详细解析了一道关于字符串处理的竞赛题目,通过建立Suffix Automaton (SAM)结构和使用线段树来求解。讨论了在处理过程中可能出现的问题,如可能的跳跃转移和不同长度子串的转移条件,并通过反证法证明了转移的正确性。最后给出了C++实现代码。
摘要由CSDN通过智能技术生成

解析

好题。
首先,我们每次都令 s i s_i si s i + 1 s_{i+1} si+1 的后缀,肯定是不劣的
问题就可以转化到 fail 树上了
首先肯定要线段树合并处理出endpos集合
朴素想法:设父亲 f a fa fa 的结束位置为 p o s f a pos_{fa} posfa,若 [ p o s f a − l e n f a + 1 , p o s f a ] [pos_{fa}-len_{fa}+1,pos_{fa}] [posfalenfa+1,posfa] 中有两个儿子 s s s,就令 a n s f a + 1 → a n s s ans_{fa}+1\to ans_{s} ansfa+1anss
但是仔细想想还会有一些问题
比如,可能会跳两次才可以转移,但是连续的父子之间都无法构成二倍关系。这个不麻烦,记录一下上一次成功转移的 p r e pre pre,每次判断 [ p o s p r e − l e n p r e + 1 , p o s p r e ] [pos_{pre}-len_{pre}+1,pos_{pre}] [posprelenpre+1,pospre] 即可

还有一个问题,我们这里的 l e n len len 都是最长长度,会不会有一个同一等价类里较短的子串可以转移,而那个最长子串无法转移呢?
不会
考虑反证证明:
假设有子串:

  1. A
  2. cA
  3. AA
    其中1、2属于同一等价类,2无法转移到3而1可以
    那么必然有串:cAA(这样12才能是一个等价类)
    又由于3是该等价类最长字符串,所以AA和cAA不属于同一等价类
    也就是说有一个位置出现了AA却没有出现cAA,那么这个地方(第一个A的位置)也会出现了A却没有出现cA
    所以1、2不在一个等价类里,与题设矛盾,原命题得证

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
const int N=4e5+100;
const int mod=1e9+7;
inline ll read(){
	ll x(0),f(1);char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return x*f;
}
int n,m;

int ed[N];
struct SAM{
	struct node{
		int len,fa;
		int tr[26];
	}st[N];
	int lst=1,tot=1;
	int pl[N];
	inline void ins(int c,int o){
		c-='a';
		int cur=++tot,p=lst;lst=tot;ed[cur]=o;
		st[cur].len=st[p].len+1;
		for(;p&&!st[p].tr[c];p=st[p].fa) st[p].tr[c]=cur;
		if(!st[p].tr[c]) st[cur].fa=1;
		else{
			int q=st[p].tr[c];
			if(st[q].len==st[p].len+1) st[cur].fa=q;
			else{
				int pp=++tot;st[pp]=st[q];
				ed[pp]=ed[q];
				st[pp].len=st[p].len+1;
				st[cur].fa=st[q].fa=pp;
				for(;p&&st[p].tr[c]==q;p=st[p].fa) st[p].tr[c]=pp;
			}
		}
	}
	void print(){
		for(int i=1;i<=tot;i++){
			printf("i=%d fa=%d len=%d ed=%d\n",i,st[i].fa,st[i].len,ed[i]);
		}
		putchar('\n');
		return;
	}
}s;

struct segmentTree{
	#define mid ((l+r)>>1)
	struct tree{
		int ls,rs,siz;
	}tr[N<<6];
	int rt[N],tot;
	inline int copy(int x){
		tr[++tot]=tr[x];return tot;
	}
	inline void pushup(int k){
		tr[k].siz=tr[tr[k].ls].siz+tr[tr[k].rs].siz;return;
	}
	void add(int &k,int l,int r,int p){
		if(!k) k=copy(0);
		if(l==r){
			tr[k].siz++;return;
		}
		if(p<=mid) add(tr[k].ls,l,mid,p);
		else add(tr[k].rs,mid+1,r,p);
		pushup(k);
	}
	int merge(int a,int b,int l,int r){
		if(!a||!b) return a|b;
		a=copy(a);
		if(l==r){
			tr[a].siz+=tr[b].siz;return a;
		}
		tr[a].ls=merge(tr[a].ls,tr[b].ls,l,mid);
		tr[a].rs=merge(tr[a].rs,tr[b].rs,mid+1,r);
		pushup(a);
		return a;
	}
	int ask(int k,int l,int r,int x,int y){
		if(x>y) return 0;
		if(!k) return 0;
		if(x<=l&&r<=y) return tr[k].siz;
		int res(0);
		if(x<=mid) res+=ask(tr[k].ls,l,mid,x,y);
		if(y>mid) res+=ask(tr[k].rs,mid+1,r,x,y);
		return res;
	}
}t;

char ss[N];

vector<int>v[N];
int dp[N],pre[N];
void dfs1(int x){
	if(ed[x]) t.add(t.rt[x],1,n,ed[x]);
	for(int to:v[x]){
		//printf("%d -> %d\n",x,to);
		dfs1(to);
		t.rt[x]=t.merge(t.rt[x],t.rt[to],1,n);
	}
	return;
}
int ans;
void dfs2(int x){
	//printf("x=%d dp=%d pre=%d\n",x,dp[x],pre[x]);
	ans=max(ans,dp[x]);
	for(int to:v[x]){
		//printf("  %d -> %d +1? (%d %d)\n",x,to,ed[to]-s.st[to].len+s.st[pre[x]].len,ed[to]-1);
		if(x==1||(ed[to]&&t.ask(t.rt[pre[x]],1,n,ed[to]-s.st[to].len+s.st[pre[x]].len,ed[to]-1))){
			dp[to]=dp[x]+1;pre[to]=to;
		}
		else dp[to]=dp[x],pre[to]=pre[x];
		dfs2(to);
	}
	return;
}
signed main() {
#ifndef ONLINE_JUDGE
	//freopen("a.in","r",stdin);
	//freopen("a.out","w",stdout);
#endif
//printf("%d\n",sizeof(t)/1024/1024);
	n=read();
	scanf(" %s",ss+1);
	for(int i=1;i<=n;i++) s.ins(ss[i],i);
	//s.print();
	for(int i=2;i<=s.tot;i++) v[s.st[i].fa].push_back(i);
	dfs1(1);
	dfs2(1);
	printf("%d\n",ans);
	return 0;
}
/*
21
abcaaaaabbcbacbabcbcb


*/
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值