Codeforces

Luogu

分析

为了方便,把串翻转,条件改为「使 $t_{i-1}$ 是 $t_i$ 的真子串」。

可以发现一个性质,即在一定存在一组最优解使得 $|t_i|=i$,因为某个不满足的串删去多的部分一定不会差。

于是可以考虑一个 DP。设 $dp_i$ 表示最后一个串以 $i$ 结尾时最多能选出多少个串。

然而这个似乎没法转移。冷静分析一下可以发现一定有 $dp_{i-1}\geq dp_i-1$(考虑一个最后一个串以 $i$ 结尾且包含 $dp_i$ 个串的方案,将方案中所有串删去最后一个字符后显然仍然合法,且此时构成一组最后一个串以 $i-1$ 结尾且包含 $dp_i-1$ 个串的方案)。于是我们可以从 $dp_{i-1}+1$ 往下枚举 $dp_i$ 并判断是否合法。可以发现总 check 次数是 $\mathcal{O}(n)$ 的。

考虑如何 check $dp_i=x$ 是否合法。我们相当于要判断 $s_{1..i-x}$ 的所有子串中是否有一个长度为 $x-1$ 的、满足是 $s_{i-x+1,i}$ 的子串且其结尾的 $dp$ 值 $\geq x-1$。

前面那个条件不好处理,可以转化为存在一个 $p\leq i-x$,满足前缀 $p$ 与前缀 $i$ 或前缀 $i-1$ 的最长公共后缀长度 $\geq x-1$。

注意到在整个过程中 $i-x$ 是不降的,因此我们可以用一个指针维护可能的范围;最长公共后缀的条件可以建出 SAM,在 Parent 树上找到前缀 $i$ 和前缀 $i-1$ 对应的点向上倍增找到最小的 $len_u\geq x-1$,则子树 $u$ 中所有点都满足该条件;$dp$ 值的条件可以直接查询子树中范围内的最大的 $dp$ 值并判断,DFS 序+线段树即可。

代码

// ====================================
//   author: M_sea
//   website: https://m-sea-blog.com/
// ====================================
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

int read() {
    int X=0,w=1; char c=getchar();
    while (c<'0'||c>'9') { if (c=='-') w=-1; c=getchar(); }
    while (c>='0'&&c<='9') X=X*10+c-'0',c=getchar();
    return X*w;
}

const int N=1000000+10;

int n,dp[N]; char s[N];

int last,cnt;
int fa[N],ch[N][26],len[N],pos[N];
void extend(int c) {
    int p=last,np=++cnt; last=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[p]+1==len[q]) fa[np]=q;
        else {
            int nq=++cnt; len[nq]=len[p]+1;
            memcpy(ch[nq],ch[q],sizeof(ch[q]));
            fa[nq]=fa[q],fa[q]=fa[np]=nq;
            for (;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
        }
    }
}

vector<int> E[N];
int f[20][N],dfn[N],low[N],tim=0;
void dfs(int u) {
    dfn[u]=++tim; f[0][u]=fa[u];
    for (int i=1;i<=19;++i) f[i][u]=f[i-1][f[i-1][u]];
    for (int v:E[u]) dfs(v);
    low[u]=tim;
}
int jump(int u,int lim) {
    for (int i=19;~i;--i)
        if (len[f[i][u]]>=lim) u=f[i][u];
    return u;
}

#define ls (o<<1)
#define rs (o<<1|1)
int maxv[N<<2];
void pushup(int o) { maxv[o]=max(maxv[ls],maxv[rs]); }
void modify(int o,int l,int r,int p,int w) {
    if (l==r) { maxv[o]=w; return; }
    int mid=(l+r)>>1;
    if (p<=mid) modify(ls,l,mid,p,w);
    else modify(rs,mid+1,r,p,w);
    pushup(o);
}
int query(int o,int l,int r,int ql,int qr) {
    if (!ql||!qr) return 0;
    if (ql<=l&&r<=qr) return maxv[o];
    int mid=(l+r)>>1,res=0;
    if (ql<=mid) res=max(res,query(ls,l,mid,ql,qr));
    if (qr>mid) res=max(res,query(rs,mid+1,r,ql,qr));
    return res;
}

int check(int i) {
    int a=jump(pos[i],dp[i]-1),b=jump(pos[i-1],dp[i]-1);
    return query(1,1,cnt,dfn[a],low[a])>=dp[i]-1||
           query(1,1,cnt,dfn[b],low[b])>=dp[i]-1;
}

int main() {
    n=read(); scanf("%s",s+1); reverse(s+1,s+n+1);
    last=cnt=1;
    for (int i=1;i<=n;++i) extend(s[i]-'a'),pos[i]=last;
    for (int i=2;i<=cnt;++i) E[fa[i]].emplace_back(i);
    dfs(1);
    for (int i=1,p=0;i<=n;++i) {
        dp[i]=dp[i-1]+1;
        while (!check(i)) --dp[i],++p,modify(1,1,cnt,dfn[pos[p]],dp[p]);
    }
    int ans=0;
    for (int i=1;i<=n;++i) ans=max(ans,dp[i]);
    printf("%d\n",ans);
    return 0;
}
最后修改:2020 年 05 月 30 日 05 : 04 PM