【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;
}
posted @ 2021-08-27 09:12  stoorz  阅读(126)  评论(0编辑  收藏  举报