0% found this document useful (0 votes)
2 views7 pages

Simple Competitive Algorithm - String

The document provides implementations of various string matching algorithms, including KMP for prefix arrays, Aho-Corasick for multi-pattern matching, and suffix arrays using doubling techniques. It also covers palindrome automata (PAM) and Z-function for substring matching. Additionally, it includes string hashing techniques for efficient substring comparisons.

Uploaded by

xcloudyoung
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views7 pages

Simple Competitive Algorithm - String

The document provides implementations of various string matching algorithms, including KMP for prefix arrays, Aho-Corasick for multi-pattern matching, and suffix arrays using doubling techniques. It also covers palindrome automata (PAM) and Z-function for substring matching. Additionally, it includes string hashing techniques for efficient substring comparisons.

Uploaded by

xcloudyoung
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

前缀数组KMP

namespace KMP {
// 获取前缀数组
std::vector<int> get_prefix(std::string s) {
int len = [Link]();
std::vector<int> pi(len);
for (int i = 1; i < len; i++) {
int j = pi[i - 1];
while (j && s[i] != s[j]) j = pi[j - 1];
if (s[i] == s[j]) j++;
pi[i] = j;
}
return pi;
}
// 返回文本串中模式串第一个出现的位置, 无则返回-1
int find_first(std::string pattern, std::string text) {
auto pi = get_prefix(pattern);
for (int i = 0, j = 0; i < [Link](); i++) {
while (j && text[i] != pattern[j]) j = pi[j - 1];
if (text[i] == pattern[j]) j++;
if (j == [Link]()) return i - j + 1;
}
return -1;
}
// 返回模式串在文本串中的所有出现
std::vector<int> find_all(std::string pattern, std::string text) {
std::string tem = pattern + "#" + text;
std::vector<int> pi = get_prefix(tem), ans;
for (int i = [Link]() + 1; i < [Link](); i++)
if (pi[i] == [Link]())
ans.push_back(i - [Link]() * 2);
return ans;
}
}
AC 自动机 多模式串匹配
easy

// fail指针指向所有模式串的前置中,能够匹配当前状态后缀的最长前缀.
// insert入所有的模式串后build
constexpr int len = 1e6;
namespace AC {
int tr[len + 5][26], cnt = 1;
int fail[len + 5], e[len + 5];

void insert(std::string s) {
int cur = 0;
for (auto c : s) {
if (!tr[cur][c - 'a']) tr[cur][c - 'a'] = cnt++;
cur = tr[cur][c - 'a'];
}
e[cur]++;
}

void build() {
std::queue<int> q;
for (int i = 0; i < 26; i++)
if (tr[0][i])
[Link](tr[0][i]);
while (![Link]()) {
int cur = [Link]();
[Link]();
for (int i = 0; i < 26; i++) {
if (tr[cur][i])
fail[tr[cur][i]] = tr[fail[cur]][i],
[Link](tr[cur][i]);
else
// 从当前状态,跳转到次态的后缀(修改了字典树的形态)
tr[cur][i] = tr[fail[cur]][i];
}
}
}

// 查询模式串一共在s中出现了多少次
int quiry(std::string s) {
int cur = 0, ans = 0;
for (auto c : s) {
cur = tr[cur][c - 'a'];
for (int i = cur; i && ~e[i]; i = fail[i])
ans += e[i], e[i] = -1;
}
return ans;
}
};
plus

// 查询每个模式串在文本串中出现了多少次
constexpr int MaxT = 2e6, N = 2e5, MaxS = 2e5;
int cnt[N + 5];
namespace AC {
struct Node {
int nex[26] = {};
int fail = 0;
vct<int> idx {};
int val = 0;
} node[MaxS + 5];
int root = 0, tot = 1;

void insert(str s, int idx) {


int cur = root;
for (auto& c : s) {
if (node[cur].nex[c - 'a'] == 0)
node[cur].nex[c - 'a'] = tot++;
cur = node[cur].nex[c - 'a'];
}
node[cur].idx.push_back(idx);
}

void build() {
std::queue<int> q;
for (int i = 0; i < 26; i++)
if (node[root].nex[i])
[Link](node[root].nex[i]);

while (![Link]()) {
int cur = [Link]();
auto& t = node[cur];
[Link]();

for (int i = 0; i < 26; i++) {


if ([Link][i]) {
node[[Link][i]].fail = node[[Link]].nex[i];
[Link]([Link][i]);
}
else {
[Link][i] = node[[Link]].nex[i];
}
}
}
return;
}

// 记录每个节点访问到了多少次
void quiry(str s) {
int cur = root;
for (auto& c : s) {
cur = node[cur].nex[c - 'a'];
node[cur].val++;
}
return;
}
// 所有fail失配指针构成一颗树
// 此处用链式前向星建图, 用vector也是可以的
int head[MaxS + 5], nex[MaxS + 5], to[MaxS + 5];
int num = 1;
void add(int u, int v) {
nex[num] = head[u], to[num] = v;
head[u] = num++;
}
int dfs(int cur) {
auto& t = node[cur];
for (int e = head[cur]; e; e = nex[e]) [Link] += dfs(to[e]);
for (auto& i : [Link]) cnt[i] += [Link];
return [Link];
}
void failtree() {
for (int i = 1; i < tot; i++) add(node[i].fail, i);
dfs(root);
}
};

字符串哈希
struct Hash
{
const i64 P = 1e12 + 39;
const int B = 13331;
std::vector<i64> h, p;
Hash(std::string s) : h([Link]() + 1), p([Link]() + 1) {
int n = [Link]();
p[0] = 1;
for (int i = 0; i < n; i++) {
h[i + 1] = (h[i] * B + s[i]) % P;
p[i + 1] = p[i] * B % P;
}
}
//左闭右开 从 0 开始, 返回字串的哈希值
i64 get(int l, int r) {
return (h[r] + __int128(h[l]) * (P - p[r - l])) % P;
}
};
倍增求后缀数组
// 以下代码能够求包含大小写字母和数字的字符串的后缀数组, 且按照ASCII码大小排序
std::vector<int> get(std::string &s, int n) {
std::vector<int> sa(n, 0), rk(n * 2, 0), tmp(n * 2, 0), cnt(n + 'z', 0);
// init
for (int i = 0; i < n; i++) {
cnt[s[i]]++;
}
for (int i = 1; i <= 'z'; i++) {
cnt[i] += cnt[i - 1];
}
for (int i = n - 1; i >= 0; i--) {
sa[--cnt[s[i]]] = i;
}
rk[sa[0]] = 1;
for (int i = 1, p = 1; i < n; i++) {
rk[sa[i]] = (s[sa[i]] == s[sa[i - 1]] ? p : ++p);
}

// a -> b
auto trans = [&](std::vector<int> &a, std::vector<int> &b, int d) -> void {
std::fill([Link](), [Link](), 0);
for (int i = 0; i < n; i++) {
cnt[rk[i + d]]++;
}
for (int i = 1; i <= n; i++) {
cnt[i] += cnt[i - 1];
}
for (int i = n - 1; i >= 0; i--) {
b[--cnt[rk[a[i] + d]]] = a[i];
}
};
for (int k = 1; k < n; k <<= 1) {
trans(sa, tmp, k), trans(tmp, sa, 0);

tmp[sa[0]] = 1;
for (int i = 1, p = 1; i < n; i++) {
if (rk[sa[i]] > rk[sa[i - 1]] || rk[sa[i] + k] > rk[sa[i - 1] + k]) {
p++;
}
tmp[sa[i]] = p;
}
rk = tmp;
}
return sa;
}

回文自动机PAM
回文自动机中每个节点的状态表示一个回文子串,为了区分奇长和偶长的回文串,回文自动机中还有奇根和偶根。
回文自动机中的 fail[] 指向的是当前状态代表的回文子串除自身外最长的回文子串。
// u 是PAM中状态的编号
// len是当前状态对应回文子串的长度
int tr[N][26];
int len[N], fail[N], tot = 1;
std::string s;
int getfail(int u, int i) {
while (i - len[u] - 1 < 0 || s[i - len[u] - 1] != s[i]) {
u = fail[u];
}
return u;
}

int main () {
fail[0] = 1;
len[1] = -1;
std::cin >> s;
for (int i = 0, now = 0; i < [Link](); i++) {
int c = s[i] - 'a';
int pos = getfail(now, i);
if (tr[pos][c] == 0) {
fail[++tot] = tr[getfail(fail[pos], i)][c];
tr[pos][c] = tot, len[tot] = len[pos] + 2;
}
now = tr[pos][c];
}
}

Z 函数/拓展KMP
约定
字符串的下标都是以 0 开头。
定义
对于一个长度为 n 的字符串 s, 定义函数 z[i] 表示最长的 l 使得 s[0, l − 1] = s[i, i + l − 1] (s[l, r] 表示以第 l 个字符开头第 r 个字符结尾的子
串)。
朴素算法
由定义很容易想到用两个简单的循环就可以得到 z[i] 了。
线性算法
从左往右遍历,对于每一个已经遍历到的 i 我们都可以得到一个子串 s[i, i + z[i] − 1],我们维护所有已经遍历到的子串中右边界最大的那个,将两
个边界记为 l 和 r,则在求解下一个位置 x 的 Z 函数值时,就可以分两种情况来讨论:
1. x ≤ r ,即所维护子串的右边界比 x 大,此时有 s[x, r] = s[x − l, r − l],这时我们就有了 z[x] 的下界: z[x] = min(z[x − l], r − x + 1)
。此后又分两种情况讨论:
i. z[x − l] < r − i + 1,此时直接令 z[x] = z[x − l] 就好了。
ii. 否则先令 z[x] = r − x + 1,然后按照朴素算法向后拓展。
2. 否则就按朴素算法向后拓展

可以发现 2. 和 [Link] 是可以合并的。


std::vector<int> zfun(std::string s) {
int n = [Link]();
std::vector z(n, 0);
for (int i = 1, l = 0, r = 0; i < n; i++) {
if (i <= r && z[i - l] < r - i + 1) {
z[i] = z[i - l];
}
else {
z[i] = std::max(0, r - i + 1);
while (i + z[i] < n && s[z[i]] == s[i + z[i]]) {
z[i]++;
}
l = i, r = i + z[i] - 1;
}
}
return z;
}

内层循环每次都会让右边界增大,最多增大 n 次,所以总的来说这种实现方式是线性的。

You might also like