题意:
把一个n位整数切成若干段,得到若干个整数。要求每个数都不为0,每个数都没有前缀0,且前一个数严格小于后一个数。问切割数方案取模。
\(n\le 5000\)
思路:
\(O(n^2)\) 的dp,\(f(l,r)\) 表示最后一段是 \([l,r]\) 的方案数,则答案是 \(\sum\limits _i f(i,n)\)
状态转移:若(作为一个整数的)\(a[l,i]<a[i+1,r]\) ,则 \(f(i+1,r)+=f(l,i)\)
双指针,\(l\) 从 \(i\) 开始往左走,\(r\) 从 \(i+1\) 开始走到 \(a[l,i]<a[i+1,r]\) 的第一个位置,那么 \(f(i+1,r\sim n)\) 都能被更新,也就是一个后缀被更新。开个差分数组处理区间加。
注意不能有前导零,且要初始化 \(f(1,1\sim n)=1\)
上面的dp不难想到,但是一般比较两个子串是 \(O(n)\) 的,需要加速。一个方法是 $ O(n^2)$ 预处理 \(lcp(i,j)\) 表示以 \(a_i,a_j\) 开始的最长公共子串的长度。
const int N = 5005, MOD = 1e9 + 7; int n, lcp[N][N], f[N][N], b[N]; char a[N]; void init() { for(int i = n; i; i--) for(int j = n; j; j--) if(a[i] == a[j]) lcp[i][j] = lcp[i+1][j+1] + 1; } bool ok(int l1, int r1, int l2, int r2) { //前一个是不是比后一个小 if(r1-l1 != r2-l2) return r1-l1 < r2-l2; int p = lcp[l1][l2]; if(p >= r1 - l1 + 1) return 0; return a[l1+p] < a[l2+p]; } signed main() { cin >> n >> a + 1; init(); for(int i = 1; i <= n; i++) f[1][i] = 1; for(int i = 1; i < n; i++) { if(a[i+1] == '0') continue; //不能有前导零 memset(b, 0, sizeof b); int r = i + 1; for(int l = i; l; l--) { while(r <= n && !ok(l,i,i+1,r)) r++; if(r <= n) (b[r] += f[l][i]) %= MOD; } for(int j = i + 1; j <= n; j++) (b[j] += b[j-1]) %= MOD, (f[i+1][j] += b[j]) %= MOD; } int ans = 0; for(int i = 1; i <= n; i++) (ans += f[i][n]) %= MOD; cout << ans; }