[CF700E][JZOJ5558]Cool Slogan

题目

给你一个长度为 n n 的字符串S,求最长的一个字符串序列 a[1]...a[k] a [ 1 ] . . . a [ k ] 满足序列中的每一个字符串都是 S S 的子串,且对于任意的1<i<=k都有 a[i1] a [ i − 1 ] a[i] a [ i ] 中至少出现两次。两次出现允许重叠。求最大的 k k
Easy:n4000,Hard:n2105

题解

有人想到用SA做,每次将字符串 a[i] a [ i ] 的最高的height对应的字符串成为 a[i+1] a [ i + 1 ] 。但是如果最高的height值不止一个咋办?这下GG了。

审题

题目条件:① a[i1] a [ i − 1 ] a[i] a [ i ] 中至少出现两次。
②两次出现允许重叠。
③使得 k k 最大。

解题思路

Easy档:子串可以视为S的后缀的前缀。考虑利用” a[i1] a [ i − 1 ] a[i] a [ i ] 中至少出现两次”这个条件。则相当于固定了最前面的字母后,KMP中成功地匹配了该后缀的前面几个字符?但是不要忽略条件”两次出现允许重叠”。
依据以上的分析,设 f[i,j] f [ i , j ] 表示 s[i..j] s [ i . . j ] 的最多的border次数。这显然对应着最大的 k k
f[i,j]=f[i,next[j]]+1
Hard档:不要放弃之前想到的一切。这都是有用的。
什么东西与后缀有关?并且能够通过这个算法求出最多的border次数。
可以考虑SAM的性质。如果有一次border,那么说明了某个点的fail链上的祖先y的right集中的某个元素处于某个区间。
f[x] f [ x ] 表示x点对应的字符串的最大的border次数。
y y 的right集包含了x的right集。这说明x点对应的字符串出现在哪,那里必然有 y y 对应的字符串出现。
②设pright[x],若 pright[y]p[pmxLen[x]+mnLen[y],p1] p ′ ∈ r i g h t [ y ] 且 p ′ ∈ [ p − m x L e n [ x ] + m n L e n [ y ] , p − 1 ] ,则说明 x x 可以通过y转移过来。
③要求的是最大值,所以只需记录fail链上 f f 值最大的那个即可。

SAM维护right集

根据SAM的构造,每次的np点的right集里只有一个元素,为对应的 |S| | S |
根据 fail[x] f a i l [ x ] 的right集包含了 x x 的right集,可以通过主席树合并fail[x] x x 的right集。
每次需要开新的点,表示fail[x]点合并后的新right集。

int merge(int px,int py){
    if(!px||!py)return px+py;
    int nx=++tot;
    tr[nx].sum=tr[px].sum+tr[py].sum;
    tr[nx].ls=merge(tr[px].ls,tr[py].ls);
    tr[nx].rs=merge(tr[px].rs,tr[py].rs);
    return nx;
}

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 400010
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
struct note{
    int ls,rs,sum;
};note tr[N*20];
struct note_ed{
    int to,next;
};note_ed edge[N];
int Tot,head[N],head1[N],tot;
int i,j,k,l,n,m,wz,gs,ans;
char ch,s[N];
int st[N],P[N],pos[N];
int sam[N][26],fail[N],Len[N];
int dep[N],fa[N],f[N];
int rt[N];
bool bz[N];
void update(int ps){
    tr[ps].sum=tr[tr[ps].ls].sum+tr[tr[ps].rs].sum;
}
void change(int &px,int py,int l,int r,int x){
    if(!px)px=++tot;
    tr[px]=tr[py];
    tr[px].sum++;
    if(l==r)return;
    int wz=(l+r)>>1;
    if(x<=wz)change(tr[px].ls,tr[py].ls,l,wz,x);
        else change(tr[px].rs,tr[py].rs,wz+1,r,x);
    update(px);
}
int merge(int px,int py){
    if(!px||!py)return px+py;
    int nx=++tot;
    tr[nx].sum=tr[px].sum+tr[py].sum;
    tr[nx].ls=merge(tr[px].ls,tr[py].ls);
    tr[nx].rs=merge(tr[px].rs,tr[py].rs);
    return nx;
}
int query(int ps,int l,int r,int x,int y){
    if(!ps)return 0;
    if(l==x&&r==y)return tr[ps].sum;
    int wz=(l+r)>>1;
    if(y<=wz)return query(tr[ps].ls,l,wz,x,y);else
    if(x>wz)return query(tr[ps].rs,wz+1,r,x,y);else
    return query(tr[ps].ls,l,wz,x,wz)+query(tr[ps].rs,wz+1,r,wz+1,y);
}
void lb(int x,int y){
    edge[++Tot].to=y;
    edge[Tot].next=head[x];
    head[x]=Tot;
}
int ins(int x,int wz){
    int j,np=++gs,nq,p=wz,q;
    Len[np]=Len[p]+1;
    while(p&&!sam[p][x])sam[p][x]=np,p=fail[p];
    if(!p)fail[np]=1;else{
        q=sam[p][x];
        if(Len[q]==Len[p]+1)fail[np]=q;else{
            nq=++gs;
            fo(j,0,25)sam[nq][j]=sam[q][j];
            fail[nq]=fail[q];
            fail[np]=fail[q]=nq;
            Len[nq]=Len[p]+1;
            while(p&&sam[p][x]==q)sam[p][x]=nq,p=fail[p];
        }
    }
    change(rt[np],rt[1],1,n,i);
    P[np]=i;
    return np;
}
void dg(int x){
    int i,T,y;bool p;
    st[T=1]=x;
    memcpy(head1,head,sizeof(head1));
    while(T){
        p=0;
        x=st[T];
        for(i=head1[x];i;i=edge[i].next){
            head1[x]=edge[i].next;
            st[++T]=edge[i].to;
            p=1;
            break;
        }
        if(!p){
            if(T>1){
                x=st[T-1];y=st[T];
                rt[x]=merge(rt[x],rt[y]);
                P[x]=P[x]>P[y]?P[x]:P[y];
            }
            T--;
        }
    }
}
void dg1(int x){
    int i,T,y;bool p;
    st[T=1]=x;
    memcpy(head1,head,sizeof(head1));
    memset(bz,0,sizeof(bz));
    while(T){
        p=0;
        x=st[T];
        if(!bz[x]){
            bz[x]=1;
            if(x!=1){
                pos[x]=pos[fail[x]];
                y=P[x]-Len[x]+Len[fail[pos[x]]]+1;
                if(query(rt[pos[x]],1,n,y,P[x]-1)){
                    f[x]=f[pos[x]]+1;
                    pos[x]=x;
                    ans=ans>f[x]?ans:f[x];
                }
            }
        }
        for(i=head1[x];i;i=edge[i].next){
            head1[x]=edge[i].next;
            st[++T]=edge[i].to;
            p=1;
            break;
        }
        if(!p)T--;
    }
}
int main(){
    freopen("cat.in","r",stdin);
    freopen("cat.out","w",stdout);
    ch=getchar();
    while(ch>='a'&&ch<='z')s[++n]=ch,ch=getchar();
    wz=gs=1;
    rt[1]=1;tot=1;
    fo(i,1,n)
        wz=ins(s[i]-'a',wz);
    fo(i,2,gs)lb(fail[i],i);
    dg(1);
    pos[1]=1;Len[0]=-1;
    dg1(1);
    printf("%d",ans);
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值