(五)字符串算法

kmp算法

kmp 是在 O(n+m) 的时间复杂度内求解模式串匹配问题的算法。

大致思路如下:定义 nxt[i] 表示 s[0i] 子串使得前 k 个字符恰等于后 k 个字符的最大的 k ,其中 ki+1 。发现若在某一位失配过后,我们可以令模式串等于该位置的 nxt 数组值即可。同理 nxt 数组也可以在线性时间内求出。

void kmp()
{
    int la = strlen(a + 1), lb = strlen(b + 1);
    for (int i = 2, j = 0; i <= lb; i++) {
        while (j > 0 && b[i] != b[j + 1]) j = nxt[j];
        if (b[i] == b[j + 1]) j++;
        nxt[i] = j;
    }
    for (int i = 1, j = 0; i <= la; i++) {
        while (j > 0 && a[i] != b[j + 1]) j = nxt[j];
        if (a[i] == b[j + 1]) j++;
        if (j == lb) {
            printf("%d\n", i - lb + 1);
            j = nxt[j];
        }
    }
    for (int i = 1; i <= lb; i++)
        printf("%d ", nxt[i]);
}

扩展kmp(z 函数)

exkmp 算法:

定义一个字符串的 z 函数数组为:它与它的每个后缀的 LCP 长度。

可以做到在线性时间内递推求得 z 函数数组。

(推导下标从 0 开始)

设当前要求的为 z[x],设 k 为使得 i[1,x),i+z[i]1 最大的 k,那么以下代码实现就不难得出了。

void get_z(std::string s)
{
    int sz = s.size(), k = 0;
    for (int i = 1; i < sz; i++)
    {
        if (k + z[k] > i) z[i] = std::min(z[i - k], k + z[k] - i);
        while (i + z[i] < sz && s[i + z[i]] == s[z[i]]) z[i]++;
        if (k + z[k] < i + z[i]) k = i;
    }
    z[0] = sz;
}

字符串哈希

将任意长度的字符串映射为一个非负整数,冲突概率几乎为零。

Hash 过程:首先将字符串化为一个非负整数(常见的方法有用 ACSII 码代替所有字符)。将这个非负整数看做一个 P 进制数,并对 M 取模。这里的 PM 都是我们自己选取的一个合适的数值,一般可以取 P=13331,M=264 。用 unsigned long long 存储可以避免低效的取模运算。

若我们已知字符串 S 的哈希值为 H(S),字符串 S+T 哈希值为 H(S+T),那么字符串 T 的哈希值就是 H(T)=H(S+T)H(S)plen(T)(modM)

于是我们可以 O(N) 预处理所有前缀的哈希值,O(1) 查询任意子串的哈希值。

AC 自动机

输出包含 mm 行,依次为删除每个元素之前,逆序对的个数。

AC 自动机通过结合 triekmp 匹配算法来进行多模式匹配。

一个节点 u 定义为从根节点到其的字符串,也称其为状态。

失配指针的构建:AC 自动机的 Fail 指针定义为当前状态 u 的存在于 trie 树中的最长后缀。

欲求当前状态 uFail 值,设 u 的父节点为 p 。若 tr[Fail[p]][ch] 存在,那么令 Fail[u]=tr[Fail[p]][ch],相当于在其父状态上添一个字符。
否则就令 tr[u][ch] 指向 tr[Fail[u]][ch]

最后再套一个 BFS 就好了。

[模板] AC 自动机(简单版)

询问时,考虑若一个子串能匹配成功,那么它的 Fail 一定也能匹配成功,于是我们不断的跳 Fail 就可以统计不同字符串的出现次数了。

评测记录 - 洛谷

[模板] AC 自动机(加强版)

求最大出现次数,一种暴力的方法就是类似于上题直接跳 fail 数组,但是由于不打标记,时间复杂度最坏情况下并不能通过(好像数据很水,这样写也能过)。

考虑如下结论,所有 fail 指针构成了一棵树。

因为 trie 根节点没有 fail 指针,剩下 n1 个指针恰构成 n1 条边。n 个节点,n1 条边且图连通就是一棵树了。

于是每次就只用在当前节点打上标记,最后再用树形 DP 跑一遍就好了。

[模板] AC 自动机(二次加强版)

和上题差不多,加个 unordered_map 判重就可以了。

字符串模板全家桶

然鹅,并没有保证正确性,只是单纯地打了一遍。

#include <cstring>
#include <cstdio>
#include <queue>
#include <iostream>
namespace string_algorithm {
	struct kmp {
		static const int lim = 1e6;
		int nxt[lim + 5];
		void __kmp(std::string a, std::string b) {
			int az = a.size(), bz = b.size(), cnt = 0;
			nxt[0] = -1;
			for (int i = 0, j = -1; j < bz; j++) {
				while (~j && b[j + 1] != b[i]) j = nxt[j];
				if (b[j + 1] == b[i]) j++;
				nxt[i] = j;
			}
			for (int i = 0, j = -1; j < bz; j++) {
				while (~j && b[j + 1] != a[i]) j = nxt[j];
				if (b[j + 1] == a[i]) j++;
				if (j == bz) cnt++, j = nxt[j];
			}
		}
	};
	struct exkmp {
		static const int Maxn = 1e7 + 5;
		int z[Maxn];
		void exkmp_getz(std::string s) {
			int sz = s.size(), k = 0;
			for (int i = 1; i < sz; i++) {
				if (k + z[k] > i) z[i] = std::min(k + z[k] - i, z[i - k]);
				while (i + z[i] < sz && s[z[i]] == s[i + z[i]]) z[i]++;
				if (i + z[i] > k + z[k]) k = i;
			}
			z[0] = sz;
		}
	};
	
	struct hash {
		static const int Maxn = 1e6 + 5;
		unsigned long long hash[Maxn], pw[Maxn];
		const int P = 13331;
		void Init_hash(std::string s) {
			hash[0] = int(s[0]), pw[0] = 1;
			for (int i = 1, sz = s.size(); i < sz; i++) {
				hash[i] = hash[i - 1] * P + int(s[i]);
				pw[i] = pw[i - 1] * P;
			}
		}
		unsigned long long get_hash(int l, int r) {
			if (!l) return hash[r];
			else return hash[r] - hash[l - 1] * pw[r - l + 1];
		}
	};
	struct trie {
		static const int Maxn = 1e4 + 5;
		int tr[Maxn * 50][26], exist[Maxn * 50], idx;
		void insert(std::string s) {
			int p = 1, len = s.size();
			for (int i = 0; i < s.size(); i++) {
				int ch = s[i] - 'a';
				if (!tr[p][ch]) tr[p][ch] = ++idx;
				p = tr[p][ch];
			}
			if (!exist[p]) exist[p] = 1;
		}
		bool query(std::string s) {
			int p = 1, len = s.size();
			for (int i = 0; i < s.size(); i++) {
				int ch = s[i] - 'a';
				if (!tr[p][ch]) return false;
				p = tr[p][ch];
			}
			return exist[p];
		}
	};
	struct AC_automation {
		static const int lim = 1e6;
		int tr[lim + 5][26], exists[lim + 5], fail[lim + 5], n, idx;
		void insert(std::string &s) {
			int l = int(s.length()), p = 0;
			for (int i = 0; i < l; i++) {
				int ch = s[i] - 'a';
				if (!tr[p][ch]) tr[p][ch] = ++idx;
				p = tr[p][ch];
			}
			exists[p]++;
		}
		void get_fail() {
			std::queue<int> q;
			for (int i = 0; i < 26; i++)
				if (tr[0][i]) q.push(tr[0][i]);
			while (!q.empty()) {
				int p = q.front();
				q.pop();
				for (int i = 0; i < 26; i++) {
					if (tr[p][i]) {
						q.push(tr[p][i]);
						fail[tr[p][i]] = tr[fail[p]][i];
					}
					else tr[p][i] = tr[fail[p]][i];
				}
			}
		}
		int query(std::string &t) {
			int l = int(t.length()), p = 0, res = 0;
			for (int i = 0; i < l; i++) {
				p = tr[p][t[i] - 'a'];
				for (int j = p; j && ~exists[j]; j = fail[j]) {
					res += exists[j];
					exists[j] = -1;
				}
			}
			return res;
		}
	};
};
posted @   mklzc  阅读(44)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示