• 11.3-11.4kmp专题训练


    目录

    总结

    Number Sequence

    Oulipo

    剪花布条

    Cyclic Nacklace 

    Period

     Power Strings

    Seek the Name, Seek the Fame

    Blue Jeans

    Simpsons’ Hidden Talents

     Count the string


    总结

    题单链接传送门

    kmp算法,最核心的就是理解pmt数组,pmt数组前缀后缀相等的最大长度,那么可以理解为记录为上次匹配的位置,所以每次递归的长度是j-pmt[j-1];其次就是pmt可以判断是否有循环节(n%(n−pmt[n-1])==0 && pmt[n-1]),这里的n-pmt[n-1]是最小的循环节,那么循环次数就是n/(n-pmt[n-1]).

    Number Sequence

    原题链接传送门

    kmp模板题,要注意的是这里是数字,不能让它们转化为字符串再去求(这里没仔细读题wa了很多发),直接按数组去求就好了。

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #define int long long
    7. using namespace std;
    8. const int N = 1e6 + 9;
    9. int n, m;
    10. int pmt[N];
    11. int s[N], p[N];
    12. signed main()
    13. {
    14. ios::sync_with_stdio(false);
    15. cin.tie(nullptr);
    16. int t;
    17. std::cin >> t;
    18. while (t--)
    19. {
    20. int n, m;
    21. cin >> n >> m;
    22. for (int i = 0; i < n; i++) cin >> s[i];
    23. for (int j = 0; j < m; j++) cin >> p[j];
    24. for (int i = 1, j = 0; i < m; i++)
    25. {
    26. while (j && p[i] != p[j]) j = pmt[j - 1];
    27. if (p[i] == p[j]) j++;
    28. pmt[i] = j;
    29. }
    30. bool flag = 0;
    31. for (int i = 0, j = 0; i < n; i++)
    32. {
    33. while (j && s[i] != p[j]) j = pmt[j - 1];
    34. if (s[i] == p[j]) j++;
    35. if (j == m){
    36. flag = true;
    37. cout << i - j + 2 << "\n";
    38. break;
    39. }
    40. }
    41. if(!flag) cout << -1 << "\n";
    42. }
    43. return 0;
    44. }

    Oulipo

    原题链接传送门 

    模式串在匹配串中出现的次数,模板题。

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #define int long long
    7. using namespace std;
    8. const int N = 1e6 + 9;
    9. int pmt[N];
    10. void get_pmt(const string& p) {
    11. for(int i = 1, j = 0; i < p.size(); i++)
    12. {
    13. while(j && p[i] != p[j]) j = pmt[j - 1];
    14. if(p[i] == p[j]) j ++;
    15. pmt[i] = j;
    16. }
    17. }
    18. int kmp(const string& s, const string& p) {
    19. int ans = 0;
    20. for(int i = 0, j = 0; i < s.size(); i++)
    21. {
    22. while(j && s[i] != p[j]) j = pmt[j - 1];
    23. if(s[i] == p[j]) j ++;
    24. if(j == p.size()){
    25. ans++;
    26. j = pmt[j - 1];
    27. }
    28. }
    29. return ans;
    30. }
    31. signed main()
    32. {
    33. ios::sync_with_stdio(false);
    34. cin.tie(nullptr);
    35. int t;
    36. std::cin >> t;
    37. while(t--){
    38. string p, s;
    39. cin >> p >> s;
    40. get_pmt(p);
    41. cout << kmp(s, p) << endl;
    42. }
    43. return 0;
    44. }

    剪花布条

    原题链接传送门 

    一个字符串能分成相同的几段,这里要注意由于不能有重叠部分所以在递归匹配时重新从0开始即可。

    1. #include
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. const int N = 1e6 + 9;
    7. int pmt[N];
    8. void get_pmt(const string& s) {
    9. for(int i = 1, j = 0; i < s.size(); i++)
    10. {
    11. while(j && s[i] != s[j]) j = pmt[j - 1];
    12. if(s[i] == s[j]) j ++;
    13. pmt[i] = j;
    14. }
    15. }
    16. int kmp(const string& s, const string& p) {
    17. int ans = 0;
    18. for(int i = 0, j = 0; i < s.size(); i++)
    19. {
    20. while(j && s[i] != p[j]) j = pmt[j - 1];
    21. if(s[i] == p[j]) j ++;
    22. if(j == p.size())
    23. {
    24. ans++;
    25. // j = pmt[j - 1];
    26. j = 0;
    27. }
    28. }
    29. return ans;
    30. }
    31. int main()
    32. {
    33. ios::sync_with_stdio(false);
    34. cin.tie(0);
    35. string s, p;
    36. while(cin >> s)
    37. {
    38. if(s == "#") break;
    39. cin >> p;
    40. get_pmt(s);
    41. int m = p.size();
    42. cout << kmp(s, p) << endl;
    43. }
    44. return 0;
    45. }

    Cyclic Nacklace 

    原题链接传送门 

    题意:一个字符串最少加多少个字符能使字符串变成周期循环.

    思路:最小循环节为n-pmt[n-1],所以加的最小长度就是n-pmt[n-1]-pmt[n-1]%(n-pmt[n-1]).

    1. #include
    2. using namespace std;
    3. const int N = 1e6 + 9;
    4. int n, m;
    5. int pmt[N];
    6. char s[N];
    7. signed main()
    8. {
    9. int t;
    10. scanf("%d",&t);
    11. while(t--) {
    12. scanf("%s",s);
    13. int n = strlen(s);
    14. for(int i = 1, j = 0; i < n; i++){
    15. while(j && s[i] != s[j]) j = pmt[j - 1];
    16. if(s[i] == s[j]) j ++;
    17. pmt[i] = j;
    18. }
    19. int l = pmt[n - 1], r = n - l;
    20. if(l && l % r == 0) cout << 0 << endl;
    21. else cout << r - l % r << endl;
    22. }
    23. return 0;
    24. }

    Period

    原题链接传送门 

    一个字符串,问在所有的[0, i]区间是否有完整的循环节,同样利用了pmt对循环节的求解。

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. using namespace std;
    7. const int N = 1e6 + 9;
    8. int n, m;
    9. int pmt[N];
    10. signed main()
    11. {
    12. ios::sync_with_stdio(false);
    13. cin.tie(nullptr);
    14. int n;
    15. int test = 0;
    16. while(cin >> n && n != 0) {
    17. string s;
    18. cin >> s;
    19. for(int i = 1, j = 0; i < n; i++){
    20. while(j && s[i] != s[j]) j = pmt[j - 1];
    21. if(s[i] == s[j]) j ++;
    22. pmt[i] = j;
    23. }
    24. cout << "Test case #" << ++test << "\n";
    25. for(int i = 0; i < n; i++){
    26. int j = i + 1;
    27. int k = j - pmt[i];
    28. if(j % k == 0 && pmt[i])
    29. cout << j << " " << j / k << "\n";
    30. }
    31. cout << "\n";
    32. }
    33. return 0;
    34. }

     Power Strings

    原题链接传送门

    题意:假设s可以由t重复k次拼成,即s=tttt……tt,我们称为s=t^k,k是s的幂次。先给定一个字符串s,求最大的幂次n使得存在t满足s=t^n。先看看是不是周期循环,否输出1是输出循环次数即可。

    1. #include
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. const int N = 1e6 + 9;
    7. int pmt[N];
    8. void get_pmt(const string& s) {
    9. for(int i = 1, j = 0; i < s.size(); i++)
    10. {
    11. while(j && s[i] != s[j]) j = pmt[j - 1];
    12. if(s[i] == s[j]) j ++;
    13. pmt[i] = j;
    14. }
    15. }
    16. int main()
    17. {
    18. ios::sync_with_stdio(false);
    19. cin.tie(0);
    20. string s;
    21. while(cin >> s)
    22. {
    23. if(s == ".") break;
    24. get_pmt(s);
    25. int n = s.size();
    26. int m = n - 1;
    27. if(n % (n - pmt[m])) std::cout << "1\n";
    28. else std::cout << n / (n - pmt[m]) << "\n";
    29. }
    30. return 0;
    31. }

    Seek the Name, Seek the Fame

    原题链接传送门

    题意:在每个字符串中求出所有既是前缀又是后缀的子串长度。

    思路:pmt表示前缀与后缀相等的最大长度,递归pmt即可。

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. using namespace std;
    8. const int N = 1e6 + 9;
    9. int n, m;
    10. int pmt[N], ans[N];
    11. char s[N];
    12. signed main()
    13. {
    14. while(~scanf("%s",s)) {
    15. int n = strlen(s);
    16. for(int i = 1, j = 0; i < n; i++){
    17. while(j && s[i] != s[j]) j = pmt[j - 1];
    18. if(s[i] == s[j]) j ++;
    19. pmt[i] = j;
    20. }
    21. int cnt = 0;
    22. for(int j = n; j; j = pmt[j - 1]) ans[++cnt] = j;
    23. for(int i = cnt; i; i--) cout << ans[i] << " \n"[i == 1];
    24. }
    25. return 0;
    26. }

    Blue Jeans

    原题链接传送门

    多个字符串的公共子序列,水题暴力枚举即可。

    1. #include
    2. #include
    3. #include
    4. signed main(){
    5. int T, n;
    6. std::cin >> T;
    7. while (T--){
    8. std::cin >> n;
    9. std::string s, t;
    10. std::vector str;
    11. std::set ans;
    12. for (int i = 0; i < n; i++)
    13. std::cin >> s, str.push_back(s);
    14. bool f, fy = 0;
    15. s = str[0];
    16. for (int i = 60; i >= 3; i--){
    17. for (int j = 0; i + j <= 60; j++){
    18. f = 1, t = s.substr(j, i);
    19. for (int k = 1; k < str.size(); k++)
    20. if ((int)str[k].find(t) == -1){
    21. f = 0;
    22. break;
    23. }
    24. if (f) ans.insert(t);
    25. }
    26. if (!ans.empty()){
    27. fy = 1;
    28. break;
    29. }
    30. }
    31. std::cout << ((fy) ? (*(ans.begin())) : "no significant commonalities");
    32. std::cout << "\n";
    33. }
    34. return 0;
    35. }

    Simpsons’ Hidden Talents

    原题链接传送门

    题意:两个字符串s,t,输出同时作为s前缀和t后缀的最长字符串,然后输出该字符串的长度

    思路:根据题意,我们可以选择将两个字符拼在一起,最后找到长度不大于s或t的任意一个既是前缀又是后缀的字符串即可。

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #define int long long
    9. using namespace std;
    10. const int mod = 10007;
    11. const int N = 1e6 + 9;
    12. int n, m;
    13. int pmt[N];
    14. void get_pmt(const string& s) {
    15. for(int i = 1, j = 0; i < s.size(); i++)
    16. {
    17. while(j && s[i] != s[j]) j = pmt[j - 1];
    18. if(s[i] == s[j]) j ++;
    19. pmt[i] = j;
    20. }
    21. }
    22. signed main()
    23. {
    24. ios::sync_with_stdio(false);
    25. cin.tie(nullptr);
    26. string s, p;
    27. while(cin >> s >> p) {
    28. int lens = s.size(), lenp = p.size();
    29. s = s + p;
    30. get_pmt(s);
    31. n = s.size();
    32. int ans = pmt[n - 1];
    33. while(ans > lens || ans > lenp) ans = pmt[ans];
    34. if(ans==0) {cout<<0<continue;}
    35. cout << s.substr(0, ans) << " " << ans << endl;
    36. }
    37. return 0;
    38. }

     Count the string

    原题链接传送门

    题意:给定一个字符串s,求s的每个前缀在此字符串中出现的次数,然后对次数求和,然后再对10007取模,就是要输出的答案。

    思路:主要考察对pmt数组的理解,每个前缀至少出现一次,如果i匹配失败,会回到pmt[i-1],所以当前的字符串包含pmt[i-1]包含的前缀和它本身。

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #define int long long
    9. using namespace std;
    10. const int mod = 10007;
    11. const int N = 1e6 + 9;
    12. int n, m;
    13. int pmt[N];
    14. string s;
    15. void get_pmt(const string& s) {
    16. for(int i = 1, j = 0; i < s.size(); i++)
    17. {
    18. while(j && s[i] != s[j]) j = pmt[j - 1];
    19. if(s[i] == s[j]) j ++;
    20. pmt[i] = j;
    21. }
    22. }
    23. signed main()
    24. {
    25. ios::sync_with_stdio(false);
    26. cin.tie(nullptr);
    27. int t;
    28. cin >> t;
    29. while(t--) {
    30. cin >> n >> s;
    31. get_pmt(s);
    32. int ans = 0;
    33. for(int i = 0; i < n; i++) if(pmt[i]) ans = (ans + 1) % mod;
    34. cout << (ans + n) % mod << "\n";
    35. }
    36. return 0;
    37. }

      2022/11/5 0:45补11.3-11.4


  • 相关阅读:
    RK3568笔记分享之“如何挂载SPI FRAM铁电存储芯片”——飞凌嵌入式
    C++ 类和对象(B)
    什么耳机音质最好又不伤耳朵,什么耳机好用耳朵不疼
    【TypeScript】函数类型:返回值类型和参数类型到底如何定义?
    操作系统绪论习题
    R语言进行数据分组聚合统计变换(Aggregating transforms)、计算dataframe数据的分组独特值的个数(distinct)
    Redis 分布式锁
    上周热点回顾(4.1-4.7)
    Element-UI+Vue实现开发权限
    minikube 快速使用入门 - pod - 外传
  • 原文地址:https://blog.csdn.net/weixin_62802134/article/details/127698252