CF700E-Cool Slogans【SAM,线段树合并,dp】

正题

题目链接:https://www.luogu.com.cn/problem/CF700E


题目大意

给出一个字符串\(S\),求一个最大的\(k\)使得存在\(k\)个字符串其中\(s_1\)\(S\)的子串,\(s_{i+1}\)\(s_i\)中出现了至少\(2\)次。


解题思路

首先我们需要有两个结论

  1. \(s_{i+1}\)一定是\(s_i\)的其中一个后缀。因为如果\(s_{i+1}\)不是\(s_i\)的一个后缀,那么\(s_i\)去掉后面那一部分不会影响匹配数并且更短,也就是更优
  2. 对于\(parents\)树上的一对父子\(x,y\)\(y\)代表的所以字符串与\(x\)最长串的匹配数均相等。因为如果有不等的,那么证明\(y\)中的字符串的出现\(endpos\)集合不同,不符合\(\text{SAM}\)的定义,故不成立。

这样我们就可以在\(\text{SAM}\)上进行\(dp\)了,因为第一个结论我们可以直接在\(fail\)树上\(dp\),然后第二个结论让我们能够使用每个节点最长的串来进行匹配,因为这不会影响答案。

现在我们需要考虑如何判断一个节点的串是否在另一个它的祖先节点的串中出现了两次,首先作为后缀已经出现了一次,然后只需要判断是否包含一个出现在\([pos_x-len_x+len_y,pos_x-1]\)的串就好了,因为\(endpos\)在这个范围内出现的串一定是与字符串\(x\)相同的。

用线段树合并维护每个节点包含的串的位置就好了,还有就是要从上往下转移。

时间复杂度\(O(n\log n)\)


code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=4e5+10,M=N<<6;
int n,cnt,last,ans,c[N],p[N],f[N],top[N];
int rt[N],pos[N],len[N],fa[N],ch[N][26];
char s[N];
struct Seq_Tree{
    int w[M],ls[M],rs[M],cnt;
    int Change(int x,int L,int R,int pos){
        int y=++cnt;w[y]=w[x]+1;
        if(L==R)return y;
        int mid=(L+R)>>1;
        if(pos<=mid)ls[y]=Change(ls[x],L,mid,pos),rs[y]=rs[x];
        else ls[y]=ls[x],rs[y]=Change(rs[x],mid+1,R,pos);
        return y;
    }
    int Merge(int x,int y,int L,int R){
        if(!x||!y)return x|y;
        int p=++cnt;w[p]=w[x]+w[y];
        if(L==R)return p;
        int mid=(L+R)>>1;
        ls[p]=Merge(ls[x],ls[y],L,mid);
        rs[p]=Merge(rs[x],rs[y],mid+1,R);
        w[p]=w[ls[p]]+w[rs[p]];
        return p;
    }
    int Ask(int x,int L,int R,int l,int r){
        if(!x)return 0;
        if(L==l&&R==r)return w[x];
        int mid=(L+R)>>1;
        if(r<=mid)return Ask(ls[x],L,mid,l,r);
        if(l>mid)return Ask(rs[x],mid+1,R,l,r);
        return Ask(ls[x],L,mid,l,mid)+Ask(rs[x],mid+1,R,mid+1,r);
    }
}T;
void Insert(int c){
    int p=last,np=last=++cnt;
    len[np]=len[p]+1;
    for(;p&&!ch[p][c];p=fa[p])ch[p][c]=np;
    if(!p)fa[np]=1;
    else{
        int q=ch[p][c];
        if(len[p]+1==len[q])fa[np]=q;
        else{
            int nq=++cnt;len[nq]=len[p]+1;
            memcpy(ch[nq],ch[q],sizeof(ch[nq]));
            fa[nq]=fa[q];pos[nq]=pos[q];fa[q]=fa[np]=nq;
            for(;p&&ch[p][c]==q;p=fa[p])ch[p][c]=nq;
        }
    }
    return;
}
int main()
{
    scanf("%d",&n);cnt=last=1;
    scanf("%s",s+1);
    for(int i=1;i<=n;i++)
        Insert(s[i]-'a'),rt[last]=T.Change(rt[last],1,n,i),pos[last]=i;
    for(int i=1;i<=cnt;i++)c[len[i]]++;
    for(int i=1;i<=n;i++)c[i]+=c[i-1];
    for(int i=1;i<=cnt;i++)p[c[len[i]]--]=i;
    for(int i=cnt;i>1;i--)
        rt[fa[p[i]]]=T.Merge(rt[fa[p[i]]],rt[p[i]],1,n);
    int ans=1;
    for(int i=2;i<=cnt;i++){
        int x=p[i],y=fa[x];
        if(y==1){f[x]=1;top[x]=x;continue;}
        y=top[y];
        if(T.Ask(rt[y],1,n,pos[x]-len[x]+len[y],pos[x]-1))
            f[x]=f[y]+1,top[x]=x;
        else top[x]=y,f[x]=f[y];
        ans=max(ans,f[x]);
    }
    printf("%d\n",ans);
    return 0;
}
posted @ 2021-01-12 14:33  QuantAsk  阅读(57)  评论(0编辑  收藏  举报