字符串
hash:将字符串转为B进制数
const int B = 131;
const int M = 1e9 + 7;
#define int unsigned long long
void init(int n)
{
ba[0] = 1;
for (re i = 1; i <= n; i++)
ba[i] = ba[i - 1] * B;
}
int hash_get(char s[], int len)
{
int res = 0;
for (re i = 1; i <= len; i++)
res = res * B + s[i];
return res;
}
void hash_got(char s[], int len)
{
for (re i = 1; i <= len; i++)
ha[i] = ha[i - 1] * B + s[i];
}
signed main()
{
ans = ha[r] - ha[l - 1] * ba[r - l + 1];
return 0;
}
kmp:一对相等的真前缀与真后缀的个数(nxt数组)
void get_nxt(char s[], int len)//前缀函数
{
nxt[1] = 0;
for (re i = 2, j = 0; i <= len; i++)
{
while (j && s[i] != s[j + 1]) j = nxt[j];
if (s[i] == s[j + 1]) j++;
nxt[i] = j;
}
}
void match(char s[], int lens, char t[], int lent)//s在t中出现的次数
{
for (re i = 1; j = 0; i <= lent; i++)
{
while (j && (t[i] != s[j + 1] || j == lens)) j = nxt[j];
if (t[i] == s[j + 1]) j++;
if (j == lens)
{
++ans;
j = nxt[j];
//return i - j + 1;//s在t中出现的第一个位置
}
}
}
trie:字典树而已
struct tree
{
int kid[10];
}; tree tr[Z];
int tot;
bool end[Z];
void insert(char s[], int len)//插入一个字符串
{
int rt = 1;
for (re i = 1; i <= len; i++)
{
int ch = s[i] - '0';
if (!tr[rt].kid[ch]) tr[rt].kid[ch] = ++tot;
rt = tr[rt].kid[ch];
}
end[rt] = 1;
}
bool search(char s[], int len)//检索字符串是否存在
{
int rt = 1;
for (re i = 1; i <= len; i++)
{
int ch = s[i] - '0';
rt = tr[rt].kid[ch];
if (!rt) return false;
}
return end[rt];
}
AC自动机
kmp与trie的优美结合,相当于在trie上跑kmp。
一个重要定义:Fail指针(失配指针),指向其他路径上与该字母相同的节点。当前路径的模式串的后缀与fail指针指向的模式串前缀相同(与kmp类似)。
Fail指针的作用:在对于一个文本串匹配多个模式串时,如果当前模式串已经结束或失配,一般情况下我们需要回溯到根节点再换路递归,但是可以用fail直接跳到下一条路径,而且这条路径是紧接上一条的(并且与文本串对应),这样省去了回溯。因为对于fail指向的那一条路径的前缀已经在这时(被当前模式串)走过了,我们不需再走一遍重复路径。
在跳fail指针时不断统计答案,就可以遍历到以该字母为结尾的所有模式串。
构造Fail指针:对于每一层的fail指针,我们都需要用到上一层的fail状态,所以采取bfs,分层构造,对于第一层的fail,都指向0号根节点(它只是一个虚点)。对于一个节点x,它的fail只需要指向它父亲的fail的同字符儿子。
struct Trie
{
int kid[26];//26个字母
int fail;//失配指针
int end;//以该节点结尾的单词数量
#define son ac[rt].kid[i]
}; Trie ac[Z << 2];
int tot = 0;
inline void insert(char s[], int len)
{
int rt = 0;
for (re t = 1; t <= len; t++)
{
int i = s[t] - 'a';
if (!son) son = ++tot;//新建一个节点
rt = son;//进入下一层
}
ac[rt].end++;
}
void getfail()
{
ac[0].fail = 0;//fail结束边界
queue <int> q;
int rt = 0;
for (re i = 0; i < 26; i++)
if (son)
{
ac[son].fail = 0;//初始化失配指针
q.push(son);
}
while (!q.empty())
{
rt = q.front(); q.pop();
for (re i = 0; i < 26; i++)
{
if (son)
{
ac[son].fail = ac[ac[rt].fail].kid[i];//扩展后缀
q.push(son);
}
else son = ac[ac[rt].fail].kid[i];//保证字符串能沿着路径走完
}
}
}
inline int match(char s[], int len)
{
int rt = 0, ans = 0;
for (re t = 1; t <= len; t++)
{
rt = ac[rt].kid[s[t] - 'a'];//向下走一层
for (re j = rt; j && ac[j].end != -1; j = ac[j].fail)
ans += ac[j].end, ac[j].end = -1;//j不断跳fail直到完全失配
}
return ans;
}