“kuangbin带你飞”专题计划——专题十六 KMP & 扩展KMP & Manacher

前言

个人总结字符串学习&总结(感觉主要是总结模板)

  1. 很久没刷专题了,有点后悔没有坚持下去。现在,把字符串&计算几何&DP都给刷一遍吧,其他的比如数论图论把那种很基础的算法弄的很熟悉就ok了。
  2. 专题传送门[kuangbin带你飞]专题1-23
  3. 感觉直接刷cf上的题单应该能帮助cf快速上分?codeforces上的一个题单(简化版)

题目(kmp算法我觉得应该称为前缀函数算法)

1. Number Sequence HDU - 1711 (KMP模板题:在另一个字符串中找一个字符串出现的位置)

  1. 传送门Number Sequence HDU - 1711
  2. 题意:字符串->数字(’#’->1e7),然后就是KMP模板了(在另一个字符串中找一个字符串第一次出现的位置)
  3. 代码

#include <iostream>
#include <string>
#define dbg(x) cout << #x << "===" << x << endl
using namespace std;
const int N = 2e6 + 10;
int n, m;
int a[N], b[N];

//在线算法:即,其实可以一个字符一个字符输入
//与其说是KMP算法,不如说是前缀函数算法。而KMP也只是它的一个应用
int pi[N];  //前缀数组

//得到前缀函数,即前缀数组的值
//注意进入函数的是s还是s+1?
//当然是s,我发现,我之前就,从来没写过s+1的kmp。(之后多刷题,再说吧)
void get(char *s, int l) {
    for (int i = 1; i < l; i++) {
        int j = pi[i - 1];
        while (j > 0 && s[i] != s[j]) j = pi[j - 1];
        if (s[i] == s[j]) j++;
        pi[i] = j;
    }
}

//######################根据题目不同写不同的东西咯:

signed main() {
    int T;
    cin >> T;
    while (T--) {
        cin >> n >> m;
        for (int i = 0; i < n; i++) cin >> a[i];
        for (int i = 0; i < m; i++) cin >> b[i];
        int len = m;
        b[len++] = 1e7;
        for (int i = 0; i < n; i++) b[len++] = a[i];
        //注意是a中找b
        for (int i = 1; i < len; i++) {
            int j = pi[i - 1];
            while (j > 0 && b[i] != b[j]) j = pi[j - 1];
            if (b[i] == b[j]) j++;
            pi[i] = j;
        }
        // for (int i = 0; i < len; i++) cout << b[i] << " ";
        // cout << endl;
        // for (int i = 0; i < len; i++) cout << pi[i] << " ";
        // cout << endl;
        bool f = false;
        for (int i = 0; i < len; i++) {
            if (pi[i] == m) {
                f = true;
                cout << i - 2 * m + 1 << endl;
                break;
            }
        }
        if (!f) cout << -1 << endl;
    }
    return 0;
}

2. Oulipo HDU - 1686 (KMP模板题:求一个子串在另一个子串中出现的次数)

  1. Oulipo HDU - 1686
  2. 题意:求一个子串在另一个子串中出现的次数
  3. 题解&代码:(略)KMP模板题

3. 剪花布条 HDU - 2087 (KMP模板题:求另一个字符串中最多可以在剪出多少个该字符串)

  1. 剪花布条 HDU - 2087
  2. 题意:求另一个字符串中最多可以在剪出多少个该字符串
  3. 题解:kmp模板题,略了
  4. 代码:略了

4. Cyclic Nacklace HDU - 3746 (前缀函数模板题:求字符串周期n-pi[n-1])

  1. Cyclic Nacklace HDU - 3746
  2. 题意:求字符串周期
  3. 题解:n-pi[n-1]
  4. 代码:略,但是要注意
    1. C++就wa,G++就ac
    2. cin>>就TLE,scanf就TLE(cin>>1e7个数可能确实有点难受。不晓得正式赛的时候,等到TLE再用scanf来不来得及。习惯问题emmm

5. Period HDU - 1358 (前缀函数模板题:求字符串前缀的周期i+1-pi[i])

  1. Period HDU - 1358
  2. 题意&题解&代码:略

6. The Minimum Length HUST - 1010 (求周期水题:略了)

  1. The Minimum Length HUST - 1010

7. Power Strings POJ - 2406 (求周期水题+1,略了略了)

  1. Power Strings POJ - 2406

8. Seek the Name, Seek the Fame POJ - 2752 (深入理解前缀函数算法:打印字符串既是前缀也是后缀的所有前缀子串位置)

  1. Seek the Name, Seek the Fame POJ - 2752
  2. 题意:打印字符串既是前缀也是后缀的所有前缀子串位置
    1. 子串位置是起始位置,而且注意打印时的位置我们按下标从1开始
  3. 题解
    1. 求前缀函数的时候按下标从0开始
    2. 这个题涉及求前缀函数的算法原理
  4. 代码
#include <cstring>
#include <iostream>
#include <set>
#include <string>
#define dbg(x) cout << #x << "===" << x << endl
using namespace std;
const int N = 4e5 + 10;
char s[N];
int n;
//在线算法:即,其实可以一个字符一个字符输入
//与其说是KMP算法,不如说是前缀函数算法。而KMP也只是它的一个应用
int pi[N];  //前缀数组

//得到前缀函数,即前缀数组的值
//注意进入函数的是s还是s+1?
//当然是s,我发现,我之前就,从来没写过s+1的kmp。(之后多刷题,再说吧)
void get(char *s, int l) {
    for (int i = 1; i < l; i++) {
        int j = pi[i - 1];
        while (j > 0 && s[i] != s[j]) j = pi[j - 1];
        if (s[i] == s[j]) j++;
        pi[i] = j;
    }
}

//######################根据题目不同写不同的东西咯:
void dfs(int p) {
    if (!p) return;
    dfs(pi[p - 1]);
    printf("%d ", p);
}
signed main() {
    while (scanf("%s", s) != EOF) {
        n = strlen(s);
        get(s, n);
        dfs(n);  //遍历顺序是从大到小,打印顺序是从小到大
        puts("");
    }
    return 0;
}

9. Blue Jeans POJ - 3080 (暴力水题:求不超过10个长度为60的串的最长公共子串)

  1. Blue Jeans POJ - 3080
  2. 题意:求不超过10个长度为60的串的最长公共子串,只要求长度大于等于3的子串(如果没有输出"no significant commonalities")。如果长度相等输出字典序最小的串。
  3. 题解:也许可以用xxx算法,但是能暴力还是暴力emm
  4. 代码
#include <iostream>
#include <string>
#define dbg(x) cout << #x << "===" << x << endl;
using namespace std;
const int N = 15;
string s[N];
int n;
signed main() {
    int T;
    cin >> T;
    while (T--) {
        cin >> n;
        for (int i = 1; i <= n; i++) cin >> s[i];
        string ans, str;
        // dbg(ans.size());
        // dbg(str.size());
        bool f;
        for (int len = 3; len <= 60; len++) {
            // dbg(len);
            for (int i = 0; i + len - 1 < 60; i++) {
                str = s[1].substr(i, len);  //枚举字符串
                // dbg(str);
                f = true;

                for (int j = 2; j <= n; j++) {
                    bool f1 = false;
                    for (int ii = 0; ii + len - 1 < 60; ii++) {
                        if (s[j].substr(ii, len) == str) {
                            f1 = true;
                            break;
                        }
                    }
                    if (!f1) {
                        f = false;
                        break;
                    }
                }
                if (f) {
                    // dbg(str);
                    if (ans.size() < len)
                        ans = str;
                    else
                        ans = min(ans, str);
                }
            }
        }
        if (ans.size() < 3)
            puts("no significant commonalities");
        else
            cout << ans << endl;
    }
    return 0;
}
/*
3
2
GATACCAGATACCAGATACCAGATACCAGATACCAGATACCAGATACCAGATACCAGATA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3
GATACCAGATACCAGATACCAGATACCAGATACCAGATACCAGATACCAGATACCAGATA
GATACTAGATACTAGATACTAGATACTAAAGGAAAGGGAAAAGGGGAAAAAGGGGGAAAA
GATACCAGATACCAGATACCAGATACCAAAGGAAAGGGAAAAGGGGAAAAAGGGGGAAAA
3
CATCATCATCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
ACATCATCATAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AACATCATCATTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT
*/

10. Simpsons’ Hidden Talents HDU - 2594(kmp水题:略了)

  1. Simpsons’ Hidden Talents HDU - 2594

11. Count the string HDU - 3336(EKMP模板题:求所有前缀出现的次数||所有次数之和)

  1. Count the string HDU - 3336
  2. 题意:给一个字符串,求所有前缀出现的次数之和%10007
  3. 题解:使用EKMP中的z函数,然后求所有z[i]之和,注意z[0]=n
    1. z函数定义s[i~n-1]与s的最长公共前缀长度
    2. 比如z[2]=3,则表示s[2~n-1]包含了s[0],s[0~1],s[0~2]这三个前缀
  4. 代码
#include <iostream>
#include <string>
#define ll long long
#define dbg(x) cout << #x << "===" << x << endl
using namespace std;
const int N = 2e5 + 10;

int n;
char s[N];
//首先约定:字符串下标从0开始
int z[N];  // z[i]定义:s和s[i,n-1]的最长公共前缀的长度。
//叫做z函数,特别的z[0]=0(也不一定,有时候题目就会暗示z[0]=n,注意变通就ok了)
void z_function(char *s, int n) {
    int l = 0, r = 0;  //算法的过程中我们维护右端点最靠右的匹配段[l,r]
    for (int i = 1; i < n; i++) {
        //如果i<=r,[i,r]和[i-l,r-l]与s的最长公共前缀应该相等
        if (i <= r && z[i - l] < r - i + 1) {
            z[i] = z[i - l];
        } else {
            z[i] = max(0, r - i + 1);
            while (i + z[i] < n && s[z[i]] == s[i + z[i]]) ++z[i];
        }
        if (i + z[i] - 1 > r) l = i, r = i + z[i] - 1;
        //注意每次需要O(1)更新一下下
    }
    z[0] = n;  //有时候题目会有这个要求,但是有时候就z[0]=0
}
signed main() {
    int T;
    cin >> T;
    while (T--) {
        scanf("%d%s", &n, s);
        for (int i = 0; i < n; i++) z[i] = 0;
        z_function(s, n);
        // for (int i = 0; i < n; i++) cout << ">>>" << i << " " << z[i] <<
        // endl;
        int ans = 0;
        for (int i = 0; i < n; i++) ans = (ans + z[i]) % 10007;
        printf("%d\n", ans);
    }
    return 0;
}

12. Clairewd’s message HDU - 4300 (暂时做不出来可以留着没事想想?但是下次的第一件事情应该就是做这件事情?)

  1. Clairewd’s message HDU - 4300
  2. 题意:不超过100个测试样例,每个测试样例有一个长度为26和一个长度不超过1e5的字符串。
    1. 每个测试样例,第一个字符串26个不同小写字母表示密文,对应明文"abcdefg…",比如"qwertyuiopasdfghjklzxcvbnm",q-a,w-b,…
    2. 第二个字符为某个情报,只截取了所有密文+部分明文(可能没有明文,也可能是完整明文)。
    3. 要求求出完整的最小可能长度的"密文+明文"。
    4. 样例:
      在这里插入图片描述
  3. 题解
在ACM算法竞赛中,字符串匹配和回文查找是常见的问题类型,其中KMP算法Manacher算法是解决这类问题的重要工具。KMP算法全称为Knuth-Morris-Pratt算法,它通过预处理模式串来避免不必要的比较,显著提高了在主串中的搜索效率。算法的核心在于构建一个部分匹配表(也称为“失配函数”或“next数组”),该表记录了模式串中每个位置之前的子串中有多少个字符与前缀重合。在实际搜索过程中,一旦发生不匹配,算法就会利用这个表跳过尽可能多的字符,从而减少比较次数。 参考资源链接:[2018年更新:kuangbin分享的ACM算法模板及核心数学技巧](https://wenku.csdn.net/doc/6412b67fbe7fbd1778d46efd) 具体来说,KMP算法包含两个主要步骤:模式串预处理和模式匹配。在预处理阶段,计算部分匹配表;在匹配阶段,根据部分匹配表指导搜索过程,当模式串与主串在某个位置失配时,根据部分匹配表找到模式串中下一个可能匹配的位置,从而避免从头开始比较。KMP算法的时间复杂度为O(m+n),其中m是模式串长度,n是主串长度,相比朴素的字符串匹配算法O(m*n),效率有显著提升。 Manacher算法则是用来查找字符串中最长回文子串的高效算法。与KMP算法类似,Manacher算法也是通过避免重复计算来提高效率的,但它使用了不同的策略。Manacher算法的核心思想是利用已知的回文信息来加速查找新的回文子串。算法字符串中每个字符的左右两侧扩展,以跳过已经确定为回文的字符,并使用一个数组来记录已经找到的回文半径,避免了复杂的边界条件判断。 在实现Manacher算法时,通常引入一个虚拟字符,将原始字符串转化为所有字符都在回文中心的情况,这样每个字符都有一个对称的伴侣字符,算法通过维护一个数组来记录从每个位置出发能扩展的最大回文半径。Manacher算法的时间复杂度为O(n),其中n是字符串的长度,这是它相较于其他回文查找算法如中心扩展法的优势所在。 在ACM竞赛中,掌握KMPManacher算法对于处理字符串相关问题至关重要。通过《2018年更新:kuangbin分享的ACM算法模板及核心数学技巧》这一资料,你可以获得这些算法的具体实现和应用场景分析,加深对算法原理的理解,并在实际问题中灵活运用,以达到事半功倍的效果。 参考资源链接:[2018年更新:kuangbin分享的ACM算法模板及核心数学技巧](https://wenku.csdn.net/doc/6412b67fbe7fbd1778d46efd)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值