字符串处理
字典树Trie
/* 字典树Trie */
struct Trie {
// 最大节点数:模式串个数 * 最大串长度
static const int MAX_NODE = 10000 * 50 + 50;
static const int CHAR_SET = 26; // 字符集的大小,也是Trie树中每个节点所连的最大边数
static const int BASE = 'a';
int n; // trie大小
int id[BASE+CHAR_SET+1]; // 字符->int
int tag[MAX_NODE]; // 标记,根据需要调整
int trie[MAX_NODE][CHAR_SET];
void init() {
for (int i=0;i<CHAR_SET;++i)
id[BASE+i] = i;
n = 1;
memset(trie[0], -1, sizeof(trie[0]));
memset(tag, 0, sizeof(tag));
}
// 添加一个单词
void add(const char *s) {
int p = 0;
while (*s) {
int i = id[*s];
if (trie[p][i] == -1) {
memset(trie[n], -1, sizeof(trie[n]));
trie[p][i] = n++;
}
++s;
p = trie[p][i];
tag[p]++;
}
}
// 在字典中查询以s为前缀的单词个数
int Search(char * s) {
int p = 0;
while (*s) {
int i = id[*s];
if (trie[p][i] == -1) break;
++s;
p = trie[p][i];
}
if (*s)
return 0;
else
return tag[p];
}
};
AC自动机
/* AC自动机 解决多模式串匹配问题
* 边:值为一个字符
* 节点:值L(v)={从根节点到v的路径上所有边的值的序列}
* 根节点为空,代表初始状态。每个模式串pi都可以找到L(v) = pi。
* fail函数: 如果L(q) 是 L(v) 的一个最长后缀 则 fail(v)=q
* 时间复杂度: O(n+m+z) n:模式串总长度 m:文本长 z:文本中模式串出现次数
*/
struct ACAutomation {
// 最大节点数:模式串个数 * 最大串长度
static const int MAX_NODE = 10000 * 50 + 50;
static const int CHAR_SET = 26;
static const int BASE = 'a';
int n; // trie大小
int id[BASE+CHAR_SET+1]; // 字符->int
int tag[MAX_NODE]; // 标记,根据需要调整
int last[MAX_NODE]; // 后缀链接,链接到上一个单词节点
int fail[MAX_NODE];
int trie[MAX_NODE][CHAR_SET];
void init() {
for (int i=0;i<CHAR_SET;++i)
id[BASE+i] = i;
n = 1;
memset(trie[0], -1, sizeof(trie[0]));
memset(tag, 0, sizeof(tag));
memset(last, 0, sizeof(last));
}
void add(const char *s) {
int p = 0;
while (*s) {
int i = id[*s];
if (trie[p][i] == -1) {
memset(trie[n], -1, sizeof(trie[n]));
trie[p][i] = n++;
}
++s;
p = trie[p][i];
}
//tag[p] = 1;
tag[p]++;
}
void build() {
queue<int> Q;
fail[0] = 0;
int u, v;
for (int i=0;i<CHAR_SET;++i) {
u = trie[0][i];
if (u != -1) {
fail[u] = 0;
Q.push(u);
}
else
trie[0][i] = 0;
}
while (!Q.empty()) {
int fr = Q.front();Q.pop();
for (int i=0;i<CHAR_SET;++i) {
int &u = trie[fr][i]; // 注意这里使用了一个引用
if ( u != -1 ) {
Q.push(u);
fail[u] = trie[fail[fr]][i];
last[u] = tag[fail[u]] ? fail[u] : last[fail[u]];
}
else
u = trie[fail[fr]][i];
}
}
}
//
// 已经把所有指向“不存在”的边指向了合适的位置
// 每次在自动机上做一次转移即可
// 例子:hdu2222 统计出现过的关键词,在找到一个关键词后将清除tag标记
int search(const char * T, int len) {
int p = 0;
int ret = 0;
for (int i=0;i<len;++i) {
//cout << "on " << T[i] << endl;
if (T[i] < 'a' || T[i] > 'z') {
p = 0;
continue;
}
int c = id[T[i]];
p = trie[p][c];
int v = last[p];
// 检查当前节点是否是单词
if (tag[p]) {ret += tag[p];tag[p] = 0;}
// 沿着后缀链接走, 检查是否为单词
while (v) {
if (tag[v]) {
ret += tag[v];
tag[v] = 0;
}
v = last[v];
}
//cout << "match on node: " << p << " cnt: " << ret << endl;
}
return ret;
}
};
KMP
// KMP <span style="font-size:18px;">单模式串查找</span>
// T:Text P:pattern
struct KMP{
// 构造失配函数
void getFail(const char *P, int *F)
{
int len = strlen(P);
F[0] = 0;
F[1] = 0;
REP(i, 1, len-1) {
int j = F[i];
while(j & P[j] != P[i])
j = F[j];
F[i+1] = P[i] == P[j] ? j+1 : 0;
}
}
// 查找模式串在文本中的出现次数
int find(const char * T, const char *P, int *f) {
int n = strlen(T), m = strlen(P);
getFail(P, f);
int j = 0;
int num = 0;
REP(i, 0, n-1) {
while(j && P[j] != T[i])
j = f[j];
if (P[j] == T[i])
++j;
// 当完成了一个匹配后 状态是肯定要转移一次的
// 因为字符串的最后有 '\0' 所以不用显示写出来(在下一次调用失配函数的时候顺带完成)
// 但如果是整数列 则要显示地调用一次失配函数 j = f[j]
if (j == m)
++num;
}
return num;
}
};