题目链接:http://codeforces.com/contest/1562/problem/E
首先考虑贪心,如果选择了从 \(i\) 开始的字符串,那么选择所有 \(i\) 开始的字符串一定不会更差
设 \(dp[i]\) 表示以 \(i\) 开始的字符串为结尾的最长上升子序列的长度,如果可以从 \(j(j<i)\) 转移过来,那一定存在一个从 \(j\) 开始的字符串,使得该字符串字典序小于某个从 \(i\) 开始的字符串,直接判断复杂度是 \(O(n)\) 的,我们可以考虑求出两两后缀字符串的 \(lcp\),每次转移时比较 \(lcp\) 的下一个字符即可
求所有后缀字符串两两的 \(lcp\) 可以使用 \(dp\),设 \(dp[i][j]\) 表示后缀 \(i\) 和 \(j\) 的 \(lcp\),则 \(dp[i][j] = (dp[i+1][j+1]+1)*(s[i]==s[j])\)
时间复杂度 \(O(n^2)\)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5010;
int T, n;
int lcp[maxn][maxn], dp[maxn];
char s[maxn];
ll read(){ ll s = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar(); } while(ch >= '0' && ch <= '9'){ s = s * 10 + ch - '0'; ch = getchar(); } return s * f; }
int main(){
scanf("%d", &T);
while(T--){
scanf("%d", &n);
scanf("%s", s+1);
for(int i = n ; i >= 1 ; --i){
for(int j = n ; j >= 1 ; --j){
lcp[i][j] = (lcp[i+1][j+1] + 1) * (s[i] == s[j]);
}
}
for(int i = 1 ; i <= n ; ++i){
dp[i] = n - i + 1;
for(int j = 1 ; j < i ; ++j){
if(s[i+lcp[i][j]] > s[j+lcp[i][j]]) dp[i] = max(dp[i], dp[j] + n-i+1-lcp[i][j]);
}
}
int ans = 0;
for(int i = 1 ; i <= n ; ++i) ans = max(ans, dp[i]);
printf("%d\n", ans);
for(int i = 0 ; i <= n ; ++i) dp[i] = 0;
for(int i = 0 ; i <= n + 1 ; ++i){
for(int j = 0 ; j <= n + 1 ; ++j) lcp[i][j] = 0;
}
}
return 0;
}