# APCS實作題2024年6月第3題:缺字問題 > 日期:2024年7月4日 > 作者:王一哲 > 題目來源:2024年6月實作題第3題 > [ZeroJudge 題目連結](https://zerojudge.tw/ShowProblem?problemid=o078) ## 題目 ### 問題描述 給定一個大小為 $K$ 個字母的集合和字串 $S$,求出在使用該集合所組出長度為 $L$ 字串中,不為字串 $S$ 子字串的最小字典序字串為何。 例如字母集合 $\mathrm{\{a, c, m\}}$,其能組出長度為 $2$ 的字串字典序由小到大排序為 $\mathrm{aa < ac < am < ca < cc < cm < ma < mc < mm}$。字串 $S$ 為 $\mathrm{accaamcm}$,因此 $\mathrm{ma}$ 為不在 $S$ 內的最小字典序字串。 ### 輸入說明 第一行為一個長度為 $K ~(1 \leq K \leq 10)$ 的小寫字母字串代表字母集合,保證字元不重複且照字元由小到大排序。 第二行為一個正整數 $L ~(1 \leq L \leq 8,~ 1 \leq K^L \leq 6 \times 10^5)$。 第三行為小寫英文字串 $S ~(L \leq |S| \leq 5 \times 10^5)$。 子題分數: - 20%:$|S| = 1000$。 - 80%:無限制。 <br /> ### 輸出格式 輸出滿足題目要求的最小字典序字串。 <br /> ### 範例輸入1 ``` acm 2 accaamcm ``` ### 範例輸出1 ``` ma ``` ### 範例輸入2 ``` dp 3 dddppdpd ``` ### 範例輸出2 ``` pdd ``` <br /> ## Python 程式碼 ### 寫法1 依照字典順序枚舉C能組成的子字串s,再用 in 檢查 s 是否於 S 之中。最後一筆測資會超時。 ```python= C = input() # 字母集合字串 L = int(input()) # 目標子字串長度 S = input() # 要找子字串的S K = len(C) # C的長度 idx = [0]*L # 組成子字串s時,由C取字母的索引值 for _ in range(K**L): # 最多執行 K**L 次 s = "".join([C[i] for i in idx]) # 由C取出字母組成字串s if s not in S: # 如果s不在S之中,找到答案了 print(s) # 印出s break # 中止 for 迴圈 # 更新 idx,先將最後一位加1,再檢查是否需要進位,如果不需要進位就中止 for 迴圈 idx[-1] += 1 for k in range(1, L): if idx[L-k] == K: idx[L-k] -= K idx[L-k-1] += 1 else: break ``` <br /><br /> ### 寫法2 程式運作邏輯與寫法1相同,但是先枚舉S所有的子字串存入集合 sub,再檢查C組成的子字串s是否已於 sub 之中,執行速度較快。費時最久約為 0.6 s,使用記憶體最多約為 17 MB,通過測試。 ```python= C = input() # 字母集合字串 L = int(input()) # 目標子字串長度 S = input() # 要找子字串的S K = len(C) # C的長度 N = len(S) # S的長度 idx = [0]*L # 組成子字串s時,由C取字母的索引值 sub = set() # S所有的子字串集合 # 掃過S,將所有的子字串加入 sub for i in range(N-L+1): sub.add(S[i:i+L]) for _ in range(K**L): # 最多執行 K**L 次 s = "".join([C[i] for i in idx]) # 由C取出字母組成字串s if s not in sub: # 如果s不在sub之中,找到答案了 print(s) # 印出s break # 中止 for 迴圈 # 更新 idx,先將最後一位加1,再檢查是否需要進位,如果不需要進位就中止 for 迴圈 idx[-1] += 1 for k in range(1, L): if idx[L-k] == K: idx[L-k] -= K idx[L-k-1] += 1 else: break ``` <br /><br /> ## C++ 程式碼 ### 寫法1 依照字典順序枚舉C能組成的子字串s,再用 find 檢查 s 是否於 S 之中。最後一筆測資會超時。 ```cpp= #include <iostream> #include <cstring> // for memset //#include <vector> #include <string> #include <cmath> // for pow using namespace std; int main() { string C, S; // 字母集合字串C,要找子字串的S unsigned int L, K, N; // 目標子字串長度L,C的長度K,S的長度N cin >> C >> L >> S; // 讀取 C、L、S K = C.size(); N = S.size(); // 取得C、S的長度 unsigned int idx[L]; // 組成子字串s時,由C取字母的索引值 memset(idx, 0, sizeof(idx[0])*L); // 先全部設定為0 //vector<unsigned int> idx (L, 0); // 可以改用 vector,取代以上2行 /* 主要的解題過程,最多執行 K**L 次 */ for(unsigned int i=0; i<(unsigned int)pow(K, L); i++) { // 由C取出字母組成字串s string s = ""; for(unsigned int j=0; j<L; j++) s += C[idx[j]]; // 如果s不在S之中,找到答案了,印出s,中止 for 迴圈 if (S.find(s) > N) { cout << s << "\n"; break; } // 更新 idx,先將最後一位加1,再檢查是否需要進位,如果不需要進位就中止 for 迴圈 idx[L-1]++; for(unsigned int k=1; k<L; k++) { if (idx[L-k] == K) { idx[L-k] -= K; idx[L-k-1]++; } else { break; } } } return 0; } ``` <br /><br /> ### 寫法2 程式運作邏輯與寫法1相同,但是先枚舉S所有的子字串存入集合 sub,再檢查C組成的子字串s是否已於 sub 之中,執行速度較快。費時最久約為 0.3 s,使用記憶體最多約為 9.2 MB,通過測試。 ```cpp= #include <iostream> #include <cstring> // for memset #include <string> #include <cmath> // for pow #include <set> using namespace std; int main() { string C, S; // 字母集合字串C,要找子字串的S unsigned int L, K, N; // 目標子字串長度L,C的長度K,S的長度N cin >> C >> L >> S; // 讀取 C、L、S K = C.size(); N = S.size(); // 取得C、S的長度 unsigned int idx[L]; // 組成子字串s時,由C取字母的索引值 memset(idx, 0, sizeof(idx[0])*L); // 先全部設定為0 set<string> sub; for(unsigned int i=0; i<=N-L; i++) sub.insert(S.substr(i, L)); /* 主要的解題過程,最多執行 K**L 次 */ for(unsigned int i=0; i<(unsigned int)pow(K, L); i++) { // 由C取出字母組成字串s string s = ""; for(unsigned int j=0; j<L; j++) s += C[idx[j]]; // 如果s不在sub之中,找到答案了,印出s,中止 for 迴圈 if (sub.count(s) == 0) { cout << s << "\n"; break; } // 更新 idx,先將最後一位加1,再檢查是否需要進位,如果不需要進位就中止 for 迴圈 idx[L-1]++; for(unsigned int k=1; k<L; k++) { if (idx[L-k] == K) { idx[L-k] -= K; idx[L-k-1]++; } else { break; } } } return 0; } ``` <br /><br /> --- ###### tags:`APCS`、`Python`、`C++`