字符串

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;
}

 

posted @ 2022-04-09 14:09  sandom  阅读(46)  评论(4编辑  收藏  举报