题意:
把一个字符串分割成多个小串,小串组成严格递增序列,n<=5000
这是一个DP。
s代表原串
dp[i][j]代表当前到i位置最后一个串是以j为开头的方案数。答案就是dp[n][1]+...+dp[n][n]
很容易得到dp[i][j]=dp[j-1][k]+dp[j-1][k+1]……dp[j-1][j-1] (i-j=j-k) 如果s[k-1....j-1] >=s[j....i], dp[i][j]+=dp[j-1][k-1]
这样dp的递推式就有了,很容易发现dp[i][j]的递推式是一个前缀和,假设我比较s[k-1....j-1]和s[j....i]是O(1) 那么就可以在n^2的复杂度里得到答案
所以问题转化为如何预处理以x为开头的串和以y为开头的串的大小(x<y)
令r[x][y]为以x开头的串和以y开头的串经过多少长度分出大小,is[x][y]为true代表x小,为false代表y小
if(r[x-1][y-1]>0) r[x][y]=r[x-1][y-1]-1,is[x][y]=is[x-1][y-1];
else 暴力跑出r[x][y]
所以对于每一个r[1][y]我们得出r[2][y+1]...r[n-y+1][n]的复杂度是O(n)
所以求出r[1][1]....r[1][n]
然后有了所有的is(x,y) 就可以O(1)比较以x和y开头的子串大小
具体代码
#include<iostream>
#include<cstring>
#include<cstdio>
#include<map>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=5005;
const long long MOD=1000000007;
long long dp[N][N];
int r[N][N];
bool is[N][N];
char s[N];
char a[N],b[N];
int l1,l2,n;
int main()
{
int i,j,k;
while(scanf("%d",&n)!=EOF)
{
scanf("%s",s+1);
for(i=1;i<=n;i++) dp[i][1]=1;
for(j=2;j<=n;j++)
{
is[1][j]=false;
for(k=0;k+j<=n;k++)
{
if(s[1+k]>s[j+k]) {is[1][j]=false;r[1][j]=k;break;}
if(s[1+k]<s[j+k]) {is[1][j]=true;r[1][j]=k;break;}
}
if(k+j>n) r[1][j]=n+1;
}
for(k=1;k<n;k++)
{
for(i=2;i+k<=n;i++)
{
if(r[i-1][i+k-1]) {is[i][i+k]=is[i-1][i+k-1];r[i][i+k]=r[i-1][i+k-1]-1;}
else
{
is[i][i+k]=false;
for(j=0;i+j<=n&&i+k+j<=n;j++)
{
if(s[i+j]>s[i+k+j]) {is[i][i+k]=false;r[i][i+k]=j;break;}
if(s[i+j]<s[i+k+j]) {is[i][i+k]=true;r[i][i+k]=j;break;}
}
if(i+k+j>n) r[i][i+k]=n+1;
}
}
}
for(i=2;i<=n;i++)
{
k=i;
if(s[i]=='0') continue;
long long sum=0;
for(j=i;j<=n;j++)
{
if(k>0) sum+=dp[i-1][k];
if(sum>=MOD) sum%=MOD;
k--;
dp[j][i]+=sum;
if(k>0&&s[k]=='0') continue;
int flag=0;
if(k>0)
{
if(is[k][i]==true&&r[k][i]+k<i) flag=1;
}
if(flag) dp[j][i]+=dp[i-1][k];
if(dp[j][i]>=MOD) dp[j][i]%=MOD;
}
}
long long ans=0;
for(i=1;i<=n;i++)
{
ans+=dp[n][i];
if(ans>=MOD) ans%=MOD;
}
cout<<ans<<endl;
}
return 0;
}