题意:
给了一个长为 n n n的字符串,求所有的子串的最长上升子序列
范围:
n
<
=
5000
n<=5000
n<=5000
思路:
先考虑最后一个子串所在位置,显然一定是原串的某一个后缀
然后考虑倒数第二个子串所在位置,分类讨论
情况1:为最后一个子串 [l,n]的前一位[l,n-1]
情况2:为[l-k,r] , k>0
讨论情况2
设[l-k,r]为子串s2,[l,n-1]为子串s1,[1,n]为子串s0,如果s2要小于s1那么很显然不如选s1
就是说s2一定要大于s1且要小,s0,也就是s1是s2的前缀且s2和s0的最后对应的一位要s2<s0
那么很显然选s2所对应的[l-k,r]不如选[l-k,n]
结论有了,就是我选的每一个后缀[l,n]的上一个子串要么为[l,n-1]要么为另一个后缀,即若我选了某一个段[l,r]那么我一定会把[l,r+1],[l,r+2]…,[l,n]都选上
接下来就是简单dp
设dp[i]为最后子串是以i节点的后缀,那么只需要枚举前一个后缀所在的点j转移,这里要求出i点所表示的后缀和j点所表示的后缀的最长相同长度,可以二维dp预处理
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=1e9+7;
const ll N=5e3+10;
int f[10005];
char s[N];
int dp[N][N]={0};
int main()
{
int i,j,k,l,n,m,max1,t,r;
scanf("%d",&t);
while(t--){
scanf("%d",&n);
scanf("%s",s+1);
for(i=1;i<=n;i++)dp[i][n+1]=0;
for(k=1;k<=n-1;k++){
for(r=n,l=n-k;l>=1;l--,r--){
if(s[l]==s[r])
dp[l][r]=dp[l+1][r+1]+1;
else
dp[l][r]=0;
}
}
max1=-1;
for(i=1;i<=n;i++){
f[i]=n-i+1;
for(j=1;j<i;j++){
k=dp[j][i];
if(k==n-i+1)continue;
if(s[i+k]>s[j+k])f[i]=max(f[i],f[j]+n-i-k+1);
}
max1=max(max1,f[i]);
}
printf("%d\n",max1);
}
}