[CF700E][JZOJ5558]Cool Slogan (后缀自动机+线段树)

题意翻译

给出一个长度为$n$的字符串$s[1]$,由小写字母组成。定义一个字符串序列$s[1....k]$,满足性质:$s[i]$在$s[i-1]$ $(i>=2)$中出现至少两次(位置可重叠),问最大的$k$是多少,使得从$s[1]$开始到$s[k]$都满足这样一个性质。

题解

  看了一个中午的代码终于弄懂了……$yyb$大佬强无敌……

  一开始以为是SAM建好之后直接上$dp$,直接用$parent$树上的儿子节点更新父亲,因为parent树上节点不同说明出现次数必定不同。但交上去一发WA了。才发现自己这种想法不能保证$parent$树上的父亲必定在儿子中出现过超过两次……

  还是来说说正解吧。我们先对原串建好SAM,并记录下每一个节点的任意一个$endpos$。考虑一下如何判断一个串是否在另一个串中出现,如果一个串(设串$A$)在另一个串(设串$B$)中出现了大于等于两次,那么在原串的任意位置的串$B$中都出现了两次。

  于是考虑一下维护每一个点的$endpos$集合,这个只要用线段树就行了。如果$A$在$B$中出现了两次,那么$A$的$endpos$集合在$[pos[B]-len[B]+len[A],pos[B]]$中出现了至少两次(其中$pos[B]$表示$B$的任意一个$endpos$)。

  不难发现有一个$dp$,每一个$parent$树上的父亲节点都可能转移到儿子节点,于是从上到下$dp$。又因为$parent$树上父亲是儿子的严格后缀,所以必然在儿子里出现了一次,那么只要考虑$endpos[A]$中是否有元素在$[pos[B]-len[B]+len[A],pos[B]-1]$中就行了

 1 //minamoto
 2 #include<cstdio>
 3 #include<cstring>
 4 using namespace std;
 5 template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
 6 const int N=444444;
 7 int fa[N],ch[N][26],l[N],f[N],a[N],c[N],pos[N],top[N];char s[N];
 8 int last=1,cnt=1,n,ans=1;
 9 void ins(int c,int k){
10     int p=last,np=++cnt;last=np,l[np]=l[p]+1,pos[np]=k;
11     for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
12     if(!p) fa[np]=1;
13     else{
14         int q=ch[p][c];
15         if(l[q]==l[p]+1) fa[np]=q;
16         else{
17             int nq=++cnt;l[nq]=l[p]+1,pos[nq]=pos[q];
18             memcpy(ch[nq],ch[q],sizeof(ch[q]));
19             fa[nq]=fa[q],fa[np]=fa[q]=nq;
20             for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
21         }
22     }
23 }
24 void calc(){
25     for(int i=1;i<=cnt;++i) ++c[l[i]];
26     for(int i=1;i<=cnt;++i) c[i]+=c[i-1];
27     for(int i=1;i<=cnt;++i) a[c[l[i]]--]=i;
28 }
29 int L[N*25],R[N*25],rt[N],tot;
30 void modify(int &now,int l,int r,int x){
31     now=++tot;if(l==r) return;
32     int mid=l+r>>1;
33     if(x<=mid) modify(L[now],l,mid,x);
34     else modify(R[now],mid+1,r,x);
35 }
36 int merge(int x,int y){
37     if(!x||!y) return x|y;
38     int z=++tot;
39     L[z]=merge(L[x],L[y]);
40     R[z]=merge(R[x],R[y]);
41     return z;
42 }
43 int query(int x,int l,int r,int ql,int qr){
44     if(!x) return 0;if(ql<=l&&qr>=r) return 1;
45     int mid=l+r>>1;
46     if(ql<=mid&&query(L[x],l,mid,ql,qr)) return 1;
47     if(qr>mid&&query(R[x],mid+1,r,ql,qr)) return 1;
48     return 0;
49 }
50 int main(){
51     scanf("%d",&n);
52     scanf("%s",s+1);
53     for(int i=1;i<=n;++i) ins(s[i]-'a',i),modify(rt[last],1,n,i);
54     calc();
55     for(int i=cnt;i>1;--i) rt[fa[a[i]]]=merge(rt[fa[a[i]]],rt[a[i]]);
56     for(int i=2;i<=cnt;++i){
57         int u=a[i],ff=fa[u];
58         if(ff==1){f[u]=1,top[u]=u;continue;}
59         int x=query(rt[top[ff]],1,n,pos[u]-l[u]+l[top[ff]],pos[u]-1);
60         if(x) f[u]=f[ff]+1,top[u]=u;
61         else f[u]=f[ff],top[u]=top[ff];
62         cmax(ans,f[u]);
63     }
64     printf("%d\n",ans);
65     return 0;
66 }

 

转载于:https://www.cnblogs.com/bztMinamoto/p/9474242.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值