描述
给定一个数字字符串S,如果一个数字字符串(只包含0-9,可以有前导0)中出现且只出现1次S,我们就称这个字符串是好的。
例如假设S=666,则1666、03660666是好的,6666、66、123不是好的;假设S=1212,则01212、12123是好的,121212、121是不好的。
请你计算长度为N的数字字符串中,有多少个是好的。由于总数可能很大,你只需要输出总数模1000000007的余数。
输入
一个整数N和一个数字串S。
对于30%的数据,1 ≤ N ≤ 8
对于100%的数据,1 ≤ N ≤ 1000,1 ≤ |S| ≤ N。
输出
一个整数代表答案。
样例输入
6 1212
样例输出
298
阅读完题意,第一感觉是要利用动态规划和递推思想来完成该问题,但是如何设计动态规划的子问题还是挺复杂的。当知道一个长度为m的数字串的哪些信息以后,往他的尾部添加数字才能方便的往后递推出长度为m + 1的数字串的同类别信息呢。而且对解决当前的问题有益。首先需要知道长度为m的数字串中有没有出现过子串S,才方便递推m + 1的数字串的信息,当然长度为m的数字串的最后几个数字的信息也是很关键的,它们决定了在尾部添加一个新的数字后,新生成的数字串会不会包含新的S子串。
通过分析,设计如下的子问题结构,DP(l, s_l, c) l 代表数字串的长度, s_l代表数字串从尾部开始的最长S前缀的长度, c代表数字串中已经拥有的S串的个数, DP(l, s_l, c) 代表满足l, s_l, c的数字串的个数。
下面举例说明DP(l, s_l, c)对应的哪类数字串信息。
S = 1231 时, 11231 属于 DP(5, 4, 1)
S = 1231 时, 1123 属于 DP(4, 3, 0)
S = 1231 时, 112311 属于 DP(6, 1, 1)
等等等。
通过上述的分析,还有一个小问题需要解决,就是知道一个字符串尾部的S前缀长度,在往后面添加一个数字后,新的串的尾部字串能对应的S串的最长前缀长度,这个问题可以通过该问题的递归性来解决。
如果a1a2a3a4 是 S 的一个前缀字串, 如果添加的数字a5’是S的第五个数字,则a1a2a3a4a5’就是新的前缀,长度变为5.如果a5’不是S串的第五个数字,则需要重新计算尾部的最长S前缀字串,但是在计算的过程中,可以使用a4的信息来递归计算。
代码部分:
import java.Util.*;
class Const {
public static long mod = 1000000007;
};
//计算数字串S每个位置对应的前缀长度, 如123123 的子串 12312 的第五个数字为2,该位置对应的新子串长度为2,
//即不从第一个S数字开始的最长前缀子串的长度
// (1 2) 3 (1 2), 第二个括号的内容,重复了S的前缀,长度为2。
public void calcuSubChuanLength(int[] S, int[] pre_len) {
pre_len[0] = 0;
pre_len[1] = 0;
for(int i = 2; i < S.length; i++) {
int n_loc = pre_len[i - 1];
while(n_loc != 0 && S[n_loc + 1] != S[i]) { //计算S[i]的最长重复位置
n_loc = pre_len[n_loc];
}
if(n_loc == 0) { //前一个位置的数没有办法形成新的子串头
if(S[i] == S[1]) {
pre_len[i] = 1;
} else {
pre_len[i] = 0;
}
} else {
pre_len[i] = n_loc + 1;
}
}
return;
}
//随意输入一个数组,看一下输出的结果,下标为0的位置,其数字没有使用。
int[] demo_S = new int[7];
//123127
demo_S[0] = 0;
demo_S[1] = 1;
demo_S[2] = 2;
demo_S[3] = 3;
demo_S[4] = 1;
demo_S[5] = 2;
demo_S[6] = 7;
int[] demo_pre_len = new int[7];
Arrays.fill(demo_pre_len, 0);
calcuSubChuanLength(demo_S, demo_pre_len);
String demo_str = "";
String demo_ans = "";
for(int i = 1; i < demo_pre_len.length; i++) {
demo_str += " " + demo_S[i];
demo_ans += " " + demo_pre_len[i];
}
System.out.println(demo_str);
System.out.println(demo_ans);
1 2 3 1 2 7
0 0 0 1 2 0
从上面的示列中,可以形象的看出,求解的pre_len的含义,
public long slove(int[] S, int n) {
final long mod = 100000007;
int s_length = S.length - 1;
long[][][] dp = new long[n + 1][s_length + 1][2];
int[] pre_len = new int[s_length + 1];
Arrays.fill(pre_len, 0);
calcuSubChuanLength(S, pre_len);
for(int i = 0; i <= n; i++) {
for(int j = 0; j <= s_length; j++) {
Arrays.fill(dp[i][j], 0);
}
}
dp[0][0][0] = 1;
for(int l = 0; l < n; l++) {
for(int s_l = 0; s_l <= s_length; s_l++) {
for(int k = 0; k <= 1; k++) {
//对dp[l][s_l][k] 的末尾添加从0到9的数字,进行状态迁移计算
for(int num = 0; num <= 9; num++) {
if(s_l < s_length) {
if(S[s_l + 1] == num) {
if(s_l + 1 == s_length) {
if(k == 1) continue;
else {
dp[l + 1][s_l + 1][k + 1] = (dp[l + 1][s_l + 1][k + 1] + dp[l][s_l][k]) % mod;
}
} else {
dp[l + 1][s_l + 1][k] = (dp[l + 1][s_l + 1][k] + dp[l][s_l][k]) % mod;
}
} else {
int next_s_l = pre_len[s_l];
while(next_s_l != 0 && S[next_s_l + 1] != num) {
next_s_l = pre_len[next_s_l];
}
if(S[next_s_l + 1] == num) {
dp[l + 1][next_s_l + 1][k] = (dp[l + 1][next_s_l + 1][k] + dp[l][s_l][k]) % mod;
} else {
dp[l + 1][0][k] = (dp[l + 1][0][k] + dp[l][s_l][k]) % mod;
}
}
} else if(s_l == s_length) {
int next_s_l = pre_len[s_l];
while(next_s_l != 0 && S[next_s_l + 1] != num) {
next_s_l = pre_len[next_s_l];
}
if(S[next_s_l + 1] == num) {
dp[l + 1][next_s_l + 1][k] = (dp[l + 1][next_s_l + 1][k] + dp[l][s_l][k]) % mod;
} else {
dp[l + 1][0][k] = (dp[l + 1][0][k] + dp[l][s_l][k]) % mod;
}
}
}
}
}
}
long ans = 0;
for(int s_l = 0; s_l <= s_length; s_l++) {
ans = (ans + dp[n][s_l][1]) % mod;
}
return ans;
}
对6 1212的样例进行计算。
int[] S = new int[5];
S[0] = 0;
S[1] = 1;
S[2] = 2;
S[3] = 1;
S[4] = 2;
long ans = slove(S, 6);
System.out.println(ans);
298