计蒜客:String and Times(后缀自动机)

本文介绍了一种使用后缀自动机和拓扑排序算法来计算字符串中子串出现次数的方法,特别关注于出现次数在指定范围内的子串。通过构造后缀自动机并计算每个集合的出现次数,可以高效地找出所有符合条件的子串。

Now you have a string consists of uppercase letters, two integers AA and BB. We call a substring wonderful substring when the times it appears in that string is between AA and BB (A \le times \le BA≤times≤B). Can you calculate the number of wonderful substrings in that string?

Input

Input has multiple test cases.

For each line, there is a string SS, two integers AA and BB.

\sum length(S) \le 2 \times 10^6∑length(S)≤2×106,

1 \le A \le B \le length(S)1≤A≤B≤length(S)

Output

For each test case, print the number of the wonderful substrings in a line.

样例输入复制

AAA 2 3
ABAB 2 2

样例输出复制

2
3

题目来源

ACM-ICPC 2018 焦作赛区网络预赛

题意:计算出现次数为A到B的子串个数。

思路:构造后缀自动机,拓扑排序算出每个集合的出现次数,然后用当前MAX减一下父亲集合的MAX就能算出每个集合的子串数量了。

# include <iostream>
# include <cstdio>
# include <cstring>
using namespace std;
typedef long long LL;
const int maxn = 2e5+30;
char s[maxn];
int g[maxn][26], pre[maxn], mx[maxn];
int b[maxn], f[maxn], id[maxn];
int sz, last;
void newnode(int s){
    mx[++sz] = s;
    pre[sz] = 0;
    memset(g[sz], 0, sizeof(g[sz]));
}
void init(){
    sz = 0;
    last = 1;
    newnode(0);
}
int ins(int x){
    newnode(mx[last] + 1);
    int p = last, np = sz;
    while(p && !g[p][x]){
        g[p][x] = np;
        p = pre[p];
    }
    if(p){//边发生冲突
        int q = g[p][x];
        if(mx[q] == mx[p] + 1) pre[np] = q;//新增结束点,不改变原图
        else{
            newnode(mx[p] + 1);//新增点
            int nq = sz;
            for(int j=0; j<26; ++j) g[nq][j] = g[q][j];
            pre[nq] = pre[q];
            pre[q] = pre[np] = nq;
            while(p && g[p][x] == q){
                g[p][x] = nq;
                p = pre[p];
            }
        }
    }
    else
        pre[np] = 1;
    last = np;
}
int main(){
    int L, R;
    while(~scanf("%s%d%d",s+1,&L,&R)){
        init();
        memset(b, 0, sizeof(b));
        memset(f, 0, sizeof(f));
        memset(id, 0, sizeof(id));
        int len = strlen(s+1);
        for(int i=1; i<=len; ++i) ins(s[i]-'A');
        for(int i=1,p=1; i<=len; ++i)
            p = g[p][s[i]-'A'],++f[p];//主链加上1,表示至少出现1次
        for(int i=1; i<=sz; ++i) ++b[mx[i]];//基数排序
        for(int i=1; i<=len; ++i) b[i] += b[i-1];
        for(int i=1; i<=sz; ++i) id[b[mx[i]]--]=i;//排在第i的是哪个集合
        for(int i=sz; i; --i) f[pre[id[i]]] += f[id[i]];//按拓扑序累加
        LL ans = 0;
        for(int i=2; i<=sz; ++i)
            if(f[id[i]] >= L && f[id[i]] <= R)
                ans += mx[id[i]]-mx[pre[id[i]]];
        printf("%lld\n",ans);
    }
    return 0;
}

 

<think> 首先,用户查询是“我想查找AC代码的相关信息 请问AC代码介绍”,意思是“我想查找AC代码的相关信息,请介绍AC代码”。 从系统级指令中,我知道我需要使用中文回答,确保LaTeX语法正确(行内数学表达式用$...$,独立公式用$$...$$),并且生成相关问题。回答中引用的段落末尾添加引用标识。 用户提供了三个引用: - 引用[1]:关于AC自动机的详解,包括原理、优化分析和代码实现。提到了多模式串匹配的代码片段。 - 引用[2]:解释了AC自动机是在Trie树上构建的,有失败指针,并描述了构建过程。 - 引用[3]:讨论了AC自动机原理及代码实现,包括模式匹配过程。 用户说:“上述引用仅供参考,并不是用户真正的需求,请尽可能多的参考用户上一次的问题和你的回答。” 但在这个上下文中,没有提到上一次的问题或回答,所以我应该基于当前查询和提供的引用来回答。 用户查询是“AC代码”,我认为“AC代码”可能指的是“AC自动机的代码”,因为在引用中都是关于AC自动机的。AC自动机是Aho-Corasick自动机,用于多模式串匹配。 所以,我应该介绍AC自动机及其代码。 结构化我的回答: 1. **介绍AC自动机**:简要解释什么是AC自动机及其用途。 2. **原理**:基于引用,解释AC自动机是如何工作的,包括Trie树、失败指针等。 3. **代码实现**:提供或描述代码片段,从引用中提取。 4. **生成相关问题**:在回答结束时,添加相关问题部分。 确保回答真实可靠,结构清晰。 关键点来自引用: - AC自动机是在Trie树上构建的,有失败指针(引用[2])。 - 构建失败指针需要树的广度遍历(引用[2])。 - 模式匹配过程:遍历文本串,从根节点开始,根据字符跳转到子节点,然后跳失败指针收集匹配(引用[1]和[3])。 - 代码示例:引用[1]提供了伪代码。 在回答中,我应该自然地引入引用标识。例如,在描述原理时,添加[^1]、[^2]或[^3]。 生成相关问题:基于AC自动机的主题,如优化、应用、实现细节等。 最终回答结构: - 开头介绍AC自动机。 - 详细解释原理。 - 描述代码实现。 - 结束相关问题。 确保使用中文。 现在,构建回答。 </think> ### AC自动机(AC自动机代码)介绍 AC自动机(Aho-Corasick自动机)是一种高效的多模式串匹配算法,用于在单个文本串中同时查找多个模式串的出现位置。它由Alfred V. Aho和Margaret J. Corasick于1975年提出,广泛应用于字符串搜索、文本分析(如敏感词过滤)、生物信息学等领域。AC自动机基于Trie树(字典树)构建,并通过添加“失败指针”(fail pointer)来优化匹配过程,确保在匹配失败时能快速回退到最长后缀节点,避免从头开始搜索,从而提升效率。下面我将从原理、构建过程和代码实现三个方面逐步介绍[^1][^2][^3]。 #### 1. **AC自动机原理** AC自动机的核心是Trie树和失败指针机制: - **Trie树结构**:每个节点代表一个字符,从根节点到叶子节点的路径构成一个模式串。例如,模式串集合{"he", "she", "his"}构建的Trie树中,根节点连接子节点'h',再连接到'e'或'i'等。 - **失败指针(fail pointer)**:每个节点都有一个失败指针,指向当当前字符匹配失败时应跳转的节点。失败指针指向当前节点对应模式串的最长后缀节点。例如,对于节点"she"中的'e',其失败指针可能指向"he"中的'e',因为"he"是"she"的后缀[^2]。 - **匹配过程**:遍历文本串时,从根节点开始: - 如果当前字符匹配子节点,则移动到子节点。 - 如果匹配失败,则沿着失败指针跳转,直到找到匹配或回到根节点。 - 在跳转过程中,收集所有经过节点的输出(即匹配的模式串),避免重复统[^1][^3]。 数学上,匹配过程的时间复杂度为$O(n + m + k)$,其中$n$是文本串长度,$m$是所有模式串总长度,$k$是匹配次数。这比暴力匹配($O(n \times m)$)高效得多[^1]。 #### 2. **构建过程** AC自动机的构建包括两个步骤:构建Trie树和添加失败指针。 - **构建Trie树**:将所有模式串插入Trie树。例如,模式串"abc"插入后,根节点有子节点'a','a'有子节点'b',依此类推。 - **添加失败指针**(使用广度优先遍历BFS): - 根节点的失败指针为null。 - 根节点的子节点失败指针指向根节点。 - 其他节点:设当前节点为$u$,其父节点为$p$,字符为$c$。则$u$的失败指针指向$p$的失败指针节点(记作$q$)的子节点中字符$c$对应的节点;如果$q$没有字符$c$的子节点,则递归检查$q$的失败指针,直到根节点或找到匹配[^2][^3]。 - 在BFS中,队列初始化时加入根节点的所有子节点。 例如,模式串{"he", "she"}的构建过程:根节点"h"和"s"的失败指针指向根;节点"e"(在"he"中)的失败指针,通过其父节点"h"的失败指针(根)查找,但根没有直接子节点"e",因此指向根[^2]。 #### 3. **代码实现** 以下代码基于引用[1]和[3]的伪代码实现,使用C++风格,但语言中立易于理解。代码包括Trie节点结构、构建失败指针和匹配函数。 **节点结构定义**: ```cpp struct Node { int cnt; // 记录模式串结尾数(大于0表示匹配成功,-1表示已访问) Node* fail; // 失败指针 Node* son[26]; // 子节点数组(假设字符集为小写英文字母) Node() : cnt(0), fail(nullptr) { for (int i = 0; i < 26; i++) son[i] = nullptr; } }; ``` **构建失败指针(BFS遍历)**: ```cpp void buildFail(Node* root) { queue<Node*> q; for (int i = 0; i < 26; i++) { if (root->son[i]) { root->son[i]->fail = root; // 根的子节点失败指针指向根 q.push(root->son[i]); } } while (!q.empty()) { Node* curr = q.front(); q.pop(); for (int i = 0; i < 26; i++) { Node* next = curr->son[i]; if (next) { Node* temp = curr->fail; while (temp && !temp->son[i]) temp = temp->fail; // 递归查找匹配后缀 next->fail = temp ? temp->son[i] : root; // 失败指针指向匹配节点或根 q.push(next); } } } } ``` **模式匹配函数**: ```cpp int match(Node* root, string s) { int res = 0; // 匹配数 Node* cur = root; // 从根节点开始 for (char ch : s) { int idx = ch - 'a'; // 字符映射(如小写字母) // 移动当前节点:如果子节点不存在,则跳fail while (cur != root && !cur->son[idx]) { cur = cur->fail; } if (cur->son[idx]) cur = cur->son[idx]; // 匹配成功,移动 else cur = root; // 匹配失败,回根 // 跳fail链,收集所有后缀匹配 Node* temp = cur; while (temp && temp->cnt >= 0) { res += temp->cnt; // 累加匹配数 temp->cnt = -1; // 标记已访问,避免重复 temp = temp->fail; } } return res; } ``` **说明**: - 匹配函数遍历文本串`s`,时间复杂度接近$O(n)$。 - `cnt`字段用于标记模式串结尾:初始化为模式串数量,匹配后设为-1防止重复[^1][^3]。 - 实际应用中,可添加输出链表来记录匹配位置。 AC自动机通过Trie树和失败指针优化,实现了线性时间的多模式匹配,适用于大规模文本处理。如果您有特定编程语言需求或更多细节疑问,请提供更多信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值