【CF1562E】Rescue Niwen!
题目
题目链接:https://codeforces.com/contest/1562/problem/E
给定一个长度为 \(n\) 的字符串 \(s\),它的“扩展”定义为字符串序列 \(s_1\ ,\ s_1s_2\ ,\ \cdots\ ,\ s_1s_2\cdots s_n\ ,\ s_2\ ,\ s_2s_3\ ,\ \cdots\ ,\ s_2s_3\cdots s_n\ ,\ s_3\ ,\ s_3s_4\ ,\ \cdots\ ,\ s_{n−1}s_n\ ,\ s_n\)。求它的“扩展的”最长上升子序列。字符串 \(s\) 小于 \(t\) 当且仅当 \(s\) 字典序小于 \(t\)。多测。
\(Q\leq 10^3\),\(\sum n\leq 10^4\)。
思路
有一个结论:如果最长上升子序列中存在 \([l,r]\) 这个子串,那么必然会存在 \([l,r+1],[l,r+2],\cdots ,[l,n]\)。
否则你假设以 \(l\) 开头的选择的最长子串是 \([l,r]\),在最长上升子序列中下一个子串是 \([l',r']\),记这两个子串的长度分别为 \(len_1\) 和 \(len_2\):
- 如果 \(len_1\geq len_2\),那么 \(s[l':r']\) 的字典序已经严格大于 \(s[l:l+len_2-1]\) 了,所以可以在 \([l,r]\) 后面继续插入 \([l,r+1]\) 一直到 \([l,n]\)。
- 如果 \(len_1<len_2\);如果 \(s_{r+1}<s_{l'+len_1}\),那么显然可以继续取;否则可以直接不取 \([l,r]\) 这一段,因为它这部分与 \(s[l':r']\) 的前缀完全相同,直接从 \(s[l',r']\) 的某一个前缀开始取就好了。
所以可以设 \(f[i]\) 表示所有“扩展”的左端点都在 \(s\) 的前 \(i\) 位的最长上升子序列长度。预处理任意两个位置的 \(\rm lcp\),如果 \(i\) 可以插在 \(j\) 后面,有
\[f[i]=\max(f[j]+n-(i+\text{lcp}[i][j]-1))
\]
时间复杂度 \(O(n^2)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=10010;
int Q,n,ans,f[N],lcp[N][N];
char s[N];
int main()
{
scanf("%d",&Q);
while (Q--)
{
scanf("%d%s",&n,s+1);
for (int i=1;i<=n;i++)
lcp[i][n+1]=lcp[n+1][i]=0,f[i]=n-i+1;
for (int i=n;i>=1;i--)
for (int j=n;j>=1;j--)
lcp[i][j]=(s[i]==s[j]) ? lcp[i+1][j+1]+1 : 0;
for (int i=1;i<=n;i++)
for (int j=1;j<i;j++)
if (i+lcp[i][j]<=n && s[j+lcp[i][j]]<s[i+lcp[i][j]])
f[i]=max(f[i],f[j]+n-(i+lcp[i][j]-1));
ans=0;
for (int i=1;i<=n;i++)
ans=max(ans,f[i]);
cout<<ans<<"\n";
}
return 0;
}