题目
Description
给定一个长度为(n)的由['0'..'9']
组成的字符串(s), (v[i,j])表示由字符串(s)第(i)到第(j)位组成的十进制数字。
将它的某一个上升序列定义为: 将这个字符串切割成m段不含前导'0'
的串, 切点分别为(k_1, k_2dots k_{m-1}), 使得(v[1,k_1]<v[k_1 + 1, k2]<dots<v[k_{m-2}+1,k_{m-1}])。
请你求出该字符串(s)的上升序列个数, 答案对 (10^9+7) 取模。
Input
第一行一个整数(n), 表示字符串长度;
第二行(n)个['0'..'9']
内的字符, 表示给出的字符串s。
Output
仅一行表示给出字符串s的上升序列个数对(10^9+7)取模的值。
Sample Input 1
6
123434
Sample Output 1
8
Sample Input 2
8
20152016
Sample Output 2
4
HINT
(;)对于(30%)的数据满足: (nleq10);
(;)对于(100%)的数据满足: (nleq5000)。
题解
设(f_{i, j})表示前(i)个数, 最后一段长度为(j)的方案数。
那么有两种转移。
- 对于位数小于(j)的数, 显然符合题目要求。
(;)于是(f_{i, j} += sum_{k=1}^{j-1} f_{i-j, j})
可以用前缀和做到(O(1))转移。 - 对于位数等于(j)的数, 如满足(v[i-2j+1, i-j] < v[i-j+1, i]), 那么(f_{i, j} += f{i-j, j})
代码
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int mod = 1e9 + 7;
char str[5010];
int lcp[5010][5010];
int n;
bool cmp(int x, int y)
{
int l = min(y - x - 1, lcp[x][y]);
return str[x + l] < str[y + l];
}
int f[5010][5010], sum[5010][5010];
int main()
{
scanf("%d", &n);
scanf("%s", str + 1);
for (int i = n; i >= 1; i--)
for (int j = i + 1; j <= n; j++)
if (str[i] == str[j])
lcp[i][j] = lcp[i+1][j+1] + 1;
sum[0][0] = 1;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= i; j++)
{
sum[i][j] = sum[i][j-1];
if (str[i-j+1] == '0') continue;
f[i][j] = (f[i][j] + sum[i-j][min(j-1, i-j)]) % mod;
if (2 * j <= i)
if (cmp(i-2*j+1, i-j+1))
f[i][j] = (f[i][j] + f[i-j][j]) % mod;
sum[i][j] = (sum[i][j] + f[i][j]) % mod;
}
}
printf("%d
", sum[n][n]);
return 0;
}