洛谷-P14775 [ICPC 2024 Seoul R] String Rank 题解

Solution

我们约定 \(S_i=w[s:]\),\(S(t,i):=Q_t(S_i)\),\(T(i):=Q_{|w|}(S_i)\)(即 \(S_i\) 所有子序列构成的集合)。

1. 转化问题

手玩小样例。我们就考虑题面中的 \(w=\texttt{aaba},t=3\)。

\[\begin{aligned} S(3,2) &= \{\lambda, \texttt{a}, \texttt{b}, \texttt{ba}, \texttt{ab}, \texttt{aa}, \texttt{aba}\}\\ S(3,1) &= \{\lambda, \texttt{a}, \texttt{b}, \texttt{ba}, \texttt{ab}, \texttt{aa}, \texttt{aba}, \texttt{aab}, \texttt{aaa}\} \end{aligned} \]

\(S(3,1)\) 比 \(S(3,2)\) 多出了 \({\texttt{aab}},{\texttt{aaa}}\) 两个子序列。这是为什么呢?

后缀 \(\texttt{aaba}\) 可以看成在 \(\texttt{aba}\) 前面加上一个 \(\texttt{a}\)。因此,\(\texttt{aba}\) 中的每个子序列在 \(\texttt{aaba}\) 中仍然存在,而新出现的子序列必须用到最前面的 \(\texttt{a}\)。正是这样的子序列起到了区分 \(S(3,2)\) 和 \(S(3,1)\) 的作用。

从 \(S_{i+1}\) 变成 \(S_i\),只是在前面添加了一个字符 \(w_i\)。添加字符不会让原有子序列消失,因此

\[S(t,1)\supseteq\dots\supseteq S(t,n-1)\supseteq S(t,n) \]

要使 \(S(t,\cdot)\) 两两不等,当且仅当每一对相邻集合都不等。我们设 \(f_i\) 为使 \(S(t,i)\neq S(t,i+1)\) 的最小 \(t\) 值。等价地,\(f_i\) 是在 \(T(i)\) 中出现但在 \(T(i+1)\) 中不出现的最短子序列长度。答案即为 \(\max f_i\)。

2. 计算 \(f_i\)

我们首先考虑二分,但是发现不但至少多一只 \(\log\) 而且 check 极其难写,于是放弃这种方法。

往 \(S_{i+1}\) 前面加一个字符 \(w_i\) 就可以得到下一个后缀 \(S_i\),这种嵌套关系启发我们从后往前扫。设当前扫到 \(w_i\)。

分类讨论:

情况一:如果 \(w_i\) 这个字符第一次出现

那么 \(f_i=1\)。

情况二:\(w_i\) 在位置 \(i\) 之后还会再次出现
设在 \(T(i)\) 中出现但在 \(T(i+1)\) 中不出现的最短子序列为

\[s=w_iw_{p_2}w_{p_3}\dots w_{p_m} \]

由前面的分析,\(p_1=i\) 且 \(m \ge 2\)。我们需要考虑,还需要满足什么条件才能使得 \(s\in T(i)\) 且 \(s\notin T(i+1)\)。

接下来考虑第二个字符的位置 \(p_2\)。为了使 \(s\) 不出现在 \(T(i+1)\) 中,不能让它从位置 \(\operatorname{nxt}(i)\) 或更靠右的同字符位置开始匹配。所以 \(p_2\in [i+1,\operatorname{nxt}(i)]\)。

我们枚举 \(p_2\),同时使得子序列 \(w_{p_2}w_{p_3}\dots w_{p_m}\) 必须最后一次出现,在 \(p_2\) 后面不再出现。因此 \(m-1\ge f_{p_2}\) 且可以取到 \(f_{p_2}\)。

注:枚举子序列起点时要求该子序列首次/最后一次出现是一个常见的套路,这个额外的限制既不影响正确性又往往让题目更好做。

于是得到转移方程

\[f_i=1+\min_{i<j\le \operatorname{nxt}(i)}f_j \]

3. 复杂度分析

容易 \(O(n)\) 求出 \(\operatorname{nxt}\)。

对于每种字符,设它出现的位置依次为 \(x_1<x_2<\dots<x_m\)。计算转移时,内层循环的总计算量 \(x_m-x_1<n\)。

对每种字符分别计算,所有转移的总次数为 \(O(n|\Sigma|)\)。因此时间复杂度 \(O(n|\Sigma|)\)。

Code

#include <bits/stdc++.h>
#define rept(i,a,b) for(int i(a);i<=b;++i)
#define pert(i,a,b) for(int i(a);i>=b;--i)
using namespace std;
constexpr int N=3e6+5,INF=1e9;
string s;
int n,ans,f[N],p[N],l[26];
signed main(){
    cin>>s;
    n=s.size();
    fill(l,l+26,n);
    pert(i,n-1,0) p[i]=l[s[i]-'a'],l[s[i]-'a']=i;
    pert(i,n-1,0){
        if(p[i]==n) f[i]=1;
        else{
            f[i]=INF;
            rept(j,i+1,p[i]) f[i]=min(f[i],f[j]);
            ++f[i];
        }
        ans=max(ans,f[i]);
    }
    cout<<ans;
    return 0;
}
posted @ 2026-10-25 14:58  xiaoniu142857  阅读(27)  评论(0)    收藏  举报