【模板】KMP


从头到尾彻底理解KMP(2014年8月22日版)
Next[i]表示第i个字符之前的最大公共前后缀的长度
在跳转的过程当中又有定位坐标的作用,跳到匹配串的拥有相同的前缀的子串的后一个位置(因为下标从0开始)

ps:2020.12.3 好久没用kmp了果然忘了,这次请了大佬一对一辅导终于会了,大概分为以下几步
处理next数组:
匹配串 s ,当前位置 i ,当前位前一位 能够匹配到的前缀 的最后一位的 下标 j
(由于是前缀,所以s[1…j] 中的 j 也是前缀的长度)
每次循环都在问: s[ i ] 和 前缀s[ j ]的后一个位置上的字符 是否相等,
如果相等,长度+1
如果不相等,找上一个和 s[ i-1 ] 相匹配的位置,看这个位置的后一个位置上的字符是否和 s[ i ]相等,
寻找过程中,长度缩短,前缀缩短,

得到当前位置的next值,得到 s[1…i] 最长的公共前后缀的长度
如果板子下标从1开始,next的值甚至可以作为 s[i-1] 上一个相匹配的位置的下标


KMP板子1:下标从0开始

int Next[N];
//要从下标0开始存字符串
void getNext(string p) {//匹配串
    int plen = p.length();
    Next[0] = -1;
    int k = -1;//前缀指针从-1开始
    int j = 0;
    while (j < plen - 1) {
        if (k == -1 || p[k] == p[j]) {//p[k]表示前缀 p[j]表示后缀
            k++;
            j++;
            if (p[j] != p[k]) {
                Next[j] = k;
            } else {
                //如果 p[j]==p[k] 则失配后 p[next[j]]=p[k] 必然导致失配
                //这是不允许的 所以要匹配 next[j]= next[k]
                Next[j] = Next[k];
            }
        } else {
            k = Next[k];
        }
    }
}

int Kmp(string s, string p) {//模板串 要匹配的串
    int i = 0;
    int j = 0;
    int slen = s.length();
    int plen = p.length();
    while (i < slen && j < plen) {
        if (j == -1 || s[i] == p[j]) {
            i++;
            j++;
        } else {
            j = Next[j];
        }
    }
    if (j == plen) {// p串全部已经匹配上了
        return i - j;//返回s当中p的位置
    } else return -1;
}

KMP板子2:下标从1开始

struct KMP {
    vector<int> s; // 匹配串 匹配串失配得到next[]数组
    // 有些题目非字符串 也可以用kmp做 所以 用vector存
    static const int _size = 1e5 + 10;
    int next[_size], lenS;

    void getNext() {
        // 匹配串 下表从1开始
        next[1] = 0;
        for (int i = 2, j = 0; i <= lenS; i++) {
            // 找当前位置前一个匹配好的 在前缀的 后一个位置
            // 观察是否 和当前位置上的字符是一样的
            while (j > 0 && s[i] != s[j + 1]) j = next[j];
            if (s[i] == s[j + 1])j++;
            next[i] = j;
        }
    }

    // 匹配串 _s 要求下标从1开始
    void init(const vector<int> &_s) {
        lenS = _s.size() - 1;
        s = _s;
        getNext();
    }

    // 匹配串 _s 要求下标从1开始
    void init(const string &_s) {
        lenS = _s.size() - 1;
        s = vector<int>(lenS + 1);
        copy(_s.begin(), _s.end(), s.begin());
        getNext();
    }

    // 在文本串t里 找匹配串s第一次出现的位置
    // 若不存在 返回 -1
    // 文本串 t 要求下标从1开始
    int f[_size];// f[i] 表示 t[1...t] 与 s 能匹配的最长长度(s不一定都能匹配上)
    int findS(const vector<int> &t) {
        int lenT = t.size() - 1;
        for (int i = 1, j = 0; i <= lenT; i++) {
            while (j > 0 && (j == lenS || t[i] != s[j + 1])) j = next[j];
            if (t[i] == s[j + 1]) j++;
            f[i] = j;
            if (f[i] == lenS) {
                return i - lenS + 1;
            }
        }
        return -1;
    }

    int findS(const string &_t) {
        vector<int> t = vector<int>(_t.size());
        copy(_t.begin(), _t.end(), t.begin());
        return findS(t);
    }
	
	// 统计s[1...i]在t中出现的次数
	// 求 前缀长度 * 前缀出现次数 之和 
    int val[_size];//val[1...i]表示前缀s[1...i]出现的次数

    ll Count(const string &t) {
        fill(val, val + lenS + 1, 0);
        int lenT = t.size() - 1;
        for (int i = 1, j = 0; i <= lenT; ++i) {
            while (j > 0 && (j == lenS || t[i] != s[j + 1])) j = next[j];
            if (t[i] == s[j + 1]) j++;
            val[j]++; // 统计最长的前缀出现的次数
        }
        for (int i = lenS; i; --i) {
            // 每一个 长前缀 里 都会有 短前缀 的存在
            // 短前缀出现了多少次 需要从长前缀统计而来
            val[next[i]] += val[i]);
        }

        ll res = 0;
        for (int i = 1; i <= lenS; ++i) {
        	// 统计...
            // as 题目要求 求 前缀长度 * 前缀出现次数 之和
            res = Add(res, Mul(i, val[i]));
        }
        return res;
    }
} kmp;

应用:板子对应的题目 - http://acm.hdu.edu.cn/showproblem.php?pid=1711

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n, m, k;
struct KMP {
    vector<int> s; // 匹配串 匹配串失配得到next[]数组
    // 有些题目非字符串 也可以用kmp做 所以 用vector存
    static const int _size = 1e5 + 10;
    int next[_size], lenS;

    void getNext() {
        // 匹配串 下表从1开始
        next[1] = 0;
        for (int i = 2, j = 0; i <= lenS; i++) {
            // 找当前位置前一个匹配好的 在前缀的 后一个位置
            // 观察是否 和当前位置上的字符是一样的
            while (j > 0 && s[i] != s[j + 1]) j = next[j];
            if (s[i] == s[j + 1])j++;
            next[i] = j;
        }
    }

    // 匹配串 _s 要求下标从1开始
    void init(const vector<int> &_s) {
        lenS = _s.size() - 1;
        s = _s;
        getNext();
    }

    // 在文本串t里 找匹配串s第一次出现的位置
    // 若不存在 返回 -1
    // 文本串 t 要求下标从1开始
    int f[N];// f[i] 表示 t[1...t] 与 s 能匹配的最长长度(s不一定都能匹配上)
    int findS(const vector<int> &t) {
        int lenT = t.size() - 1;
        for (int i = 1, j = 0; i <= lenT; i++) {
            while (j > 0 && (j == lenS || t[i] != s[j + 1])) j = next[j];
            if (t[i] == s[j + 1]) j++;
            f[i] = j;
            if (f[i] == lenS) {
                return i - lenS + 1;
            }
        }
        return -1;
    }

} kmp;

vector<int> s, t;

void Solve() {
    cin >> n >> m;
    s = vector<int>(1);
    t = vector<int>(1);
    /*s.clear();
    t.clear();
    s.push_back(0);
    t.push_back(0);//占位*/

    for (int i = 1, x; i <= n; i++) {
        cin >> x;
        t.push_back(x);
    }

    for (int i = 1, x; i <= m; i++) {
        cin >> x;
        s.push_back(x);
    }
    kmp.init(s);// 同时 处理Next数字
    cout << kmp.findS(t) << endl;

}

void Run() {
    int T;
    cin >> T;
    for (int cs = 1; cs <= T; cs++) {
        //cout << "Case " << cs << ": ";//printf("Case %d: ", cs);
        Solve();
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
#ifndef ONLINE_JUDGE
    freopen("1.in", "r", stdin);
    freopen("debug.out", "w", stdout);
#endif

    Run();

    return 0;
}

统计后缀长度 × \times × 后缀在母串中出现次数之和
http://acm.hdu.edu.cn/showproblem.php?pid=6153

#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;

ll Add(ll a, ll b) {
    a %= mod;
    b %= mod;
    return (a + b) % mod;
}

ll Mul(ll a, ll b) {
    a %= mod;
    b %= mod;
    return a * b % mod;
}

struct KMP {
    int lenS;
    string s;
    static const int _size = 2e6 + 10;
    int next[_size];


    void init(const string &_s) {
        lenS = _s.size() - 1;
        fill(next, next + lenS + 1, 0);
        s = _s;
        getNext();
    }

    void getNext() {
        next[1] = 0;
        for (int i = 2, j = 0; i <= lenS; ++i) {
            while (j > 0 && s[i] != s[j + 1]) j = next[j];
            if (s[i] == s[j + 1]) j++;
            next[i] = j;
        }
    }

    // 统计s[1...i]在t中出现的次数
    ll val[_size];

    ll Count(const string &t) {
        fill(val, val + lenS + 1, 0);
        int lenT = t.size() - 1;
        for (int i = 1, j = 0; i <= lenT; ++i) {
            while (j > 0 && (j == lenS || t[i] != s[j + 1])) j = next[j];
            if (t[i] == s[j + 1]) j++;
            val[j]++; // 统计最长的前缀出现的次数
        }
        for (int i = lenS; i; --i) {
            // 每一个 长前缀 里 都会有 短前缀 的存在
            // 短前缀出现了多少次 需要从长前缀统计而来
            val[next[i]] = Add(val[next[i]], val[i]);
        }

        ll res = 0;
        for (int i = 1; i <= lenS; ++i) {
            // 题目要求 求 前缀长度 * 前缀出现次数 之和
            res = Add(res, Mul(i, val[i]));
        }
        return res;
    }


} kmp;

string s1, s2;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
#ifndef ONLINE_JUDGE
    freopen("1.in", "r", stdin);
    freopen("debug.out", "w", stdout);
#endif
    int T;
    cin >> T;

    while (T--) {
        cin >> s1 >> s2;
        reverse(s1.begin(), s1.end());
        reverse(s2.begin(), s2.end());
        s1 = " " + s1;
        s2 = " " + s2;
        kmp.init(s2);
        cout << kmp.Count(s1) << endl;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值