题目:
题意:
将一个纯数字串划分成若干段,要求各段代表的数值严格递增,不允许出现前导零
思路:
一开始将状态划分成[i,j,k],代表[i,j]区间段,结尾段长度为k,发现写不出转移方程,而且空间时间复杂度都不太科学
重新考虑状态,dp[i,j]代表区间段[0,i],结尾段长度为j+1(!注意,这里不是j,当然处理成j更直观), 这样可以列出状态转移方程
dp[i,j]=∑k=0j−1dp[i−j−1,k]+T
T={dp[i−j−1,j]0,str[i−j−1−j,i−j−1]<str[i−j,j],str[i−j−1−j,i−j−1]≥str[i−j,j]
显然,分情况进行讨论
- 对于任何的 dp[i-j-1,k],str[0,i-j-1]的结尾段一定比str[0,i]的结尾段小,因为k小于j,则将str[i-j, j]段并入之前的任意一种分法均可,无需判断直接状态叠加
- 对于 k==j 时的状态数 T ,需要判断str[i-j-1-j, i-j-1]段A, 与str[i-j,j]段B之间的大小关系,若A小于B同上一种情况,反之这种情况不能合并,状态数为0
- 对于 k大于j ,无法合并,因为新的结尾段肯定比之前小,不满足题意
至此,列出状态转移方程,但时间复杂度为
- 对于第一种情况, sum[i,j]=∑jk=0dp[i,k]
- 对于第二种,先对整个串求LCP(最长公共前缀),即可 O(1) 进行比较
代码:
#include <bits/stdc++.h>
#define maxn 5005
const int modn=1e9+7;
using namespace std;
char str[maxn];
long long dp[maxn][maxn]={0};
long long sum[maxn][maxn]={0};
int lcp[maxn][maxn]={0};
int n;
void getLcp(){
for(int i=n-1;i>-1;--i)
for (int j=n-1;j>-1;--j)
if (str[i] == str[j])
lcp[i][j] = lcp[i+1][j+1] + 1;
else lcp[i][j] = 0;
}
int main(){
scanf("%d",&n);
scanf("%s",str);
getLcp();
for (int i=0;i<n;++i){
dp[i][i] = 1;
sum[i][i] = 1;
}
for (int i=1;i<n;++i){
for (int j=0;j<i;++j){
if (j!=0) sum[i][j] = (sum[i][j] + sum[i][j-1]) % modn;
if (str[i-j] == '0') continue;
if (j!=0)
dp[i][j] = (dp[i][j] + sum[i-j-1][min(j-1,i-j-1)])%modn;
if (i-j-1-j >= 0){
int tlcp = lcp[i-j-1-j][i-j];
if (tlcp-1<j && str[i-j-1-j+tlcp] < str[i-j+tlcp])
dp[i][j] = (dp[i][j] + dp[i-j-1][j]) % modn;
}
sum[i][j] = (sum[i][j] + dp[i][j]) % modn;
}
sum[i][i] = (sum[i][i] + sum[i][i-1]) % modn;
}
cout<<sum[n-1][n-1]<<endl;
return 0;
}