描述
給定一個數字字符串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