CF700E Cool Slogans

45 篇文章 0 订阅
18 篇文章 0 订阅

一、题目

点此看题

二、解法

可以建好后缀自动机后 d p dp dp,容易发现我们只需要考虑一条链上的选取情况(即一定包含后缀关系,如果不包含则在另一条链上被考虑过了),设 f [ u ] f[u] f[u]为自上到下 d p dp dp u u u,所取得的最大长度,我们还要维护一个决策点, d p dp dp时需要判断父亲的决策点是否在该节点代表的串中出现了两次。

由于一定有后缀关系,我们只需要判断决策点的 e n d p o s endpos endpos集合中 [ p o s [ u ] − l e n [ u ] + l e n [ t ] , p o s [ u ] − 1 ] [pos[u]-len[u]+len[t],pos[u]-1] [pos[u]len[u]+len[t],pos[u]1]中有串出现,其中 t t t是决策点, p o s pos pos是当前点任意一个 e n d p o s endpos endpos,出现两次意味着在任意一个地方都出现了两次,看在此范围内有没有串即可,这部分需要用到线段树合并维护 e n d p o s endpos endpos集合。

如果出现了两次, f [ u ] = f [ t ] + 1 f[u]=f[t]+1 f[u]=f[t]+1 u u u的决策点为它自身,否则 f [ u ] = f [ f a ] f[u]=f[fa] f[u]=f[fa],它的决策点就是父亲的决策点,这里其实有一点贪心的意味,即我们的决策点在越上面越好。

时间复杂度 O ( n ) O(n) O(n),口胡可能难以理解的地方请参考我的代码。

可以说明一个节点的所有串是等价的,所以只需要考虑最长串。

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 400005;
int read()
{
    int x=0,flag=1;
    char c;
    while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
    while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*flag;
}
int n,m,k,tot,f[M],cnt,last,len[M],fa[M],ch[M][26];
int Index,mi[M],rt[M],ls[20*M],rs[20*M],sum[20*M];char s[M];
int dp[M],top[M],ans;
struct edge
{
    int v,next;
    edge(int V=0,int N=0) : v(V) , next(N) {}
}e[2*M];
void ins(int &x,int l,int r,int id)
{
    x=++Index;
    sum[x]=1;
    if(l==r) return ;
    int mid=(l+r)>>1;
    if(mid>=id) ins(ls[x],l,mid,id);
    else ins(rs[x],mid+1,r,id);
}
void add(int c)
{
    int p=last,np=last=++cnt;
    ins(rt[np],1,n,mi[np]=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[q]==len[p]+1) fa[np]=q;
        else
        {
            int nq=++cnt;
            fa[nq]=fa[q];
            memcpy(ch[nq],ch[q],sizeof ch[q]);
            len[nq]=len[p]+1;
            fa[q]=fa[np]=nq;
            for(; p && ch[p][c]==q; p=fa[p]) ch[p][c]=nq;
        }
    }
}
int merge(int x,int y)
{
    if(!x || !y) return x|y;
    int p=++Index;
    sum[p]=sum[x]+sum[y];
    ls[p]=merge(ls[x],ls[y]);
    rs[p]=merge(rs[x],rs[y]);
    return p;
}
int ask(int i,int l,int r,int L,int R)
{
    if(!i || L>r || l>R || L>R) return 0;
    if(L<=l && r<=R) return sum[i];
    int mid=(l+r)>>1;
    return ask(ls[i],l,mid,L,R)+ask(rs[i],mid+1,r,L,R);
}
void dfs(int u)
{
    for(int i=f[u]; i; i=e[i].next)
    {
        int v=e[i].v;
        if(v==fa[u]) continue;
        dfs(v);
        rt[u]=merge(rt[u],rt[v]);
        mi[u]=min(mi[u],mi[v]);
    }
}
void dfs2(int u)
{
    int t=top[fa[u]];
    if(fa[u]==1)
    {
        dp[u]=1;
        top[u]=u;
    }
    else
    {
        if(ask(rt[t],1,n,mi[u]-len[u]+len[t],mi[u]-1))
            dp[u]=dp[t]+1,top[u]=u;
        else
            dp[u]=dp[fa[u]],top[u]=top[fa[u]];
    }
    ans=max(ans,dp[u]);
    for(int i=f[u];i;i=e[i].next)
    {
        int v=e[i].v;
        if(v==fa[u]) continue;
        dfs2(v);
    }
}
int main()
{
    cnt=last=1;
    n=read();scanf("%s",s);
    for(int i=0;i<n;i++) add(s[i]-'a');
    for(int i=2;i<=cnt;i++)
    {
        if(!mi[i]) mi[i]=n+1;
        e[++tot]=edge(i,f[fa[i]]),f[fa[i]]=tot;
        e[++tot]=edge(fa[i],f[i]),f[i]=tot;
    }
    dfs(1);
    ans=1;
    dfs2(1);
    printf("%d\n",ans);
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值