很早以前就想写这题了,但一直鸽到今天,不过对AC自动机的理解更加到位了的说
首先我们把原串的AC自动机建出来,由于这里的删除是回退操作,因此我们记录一下每个点的父亲,遇到B
就把指针移到父亲节点即可
考虑AC自动机的性质:
- 在Trie树上一个点的祖先所代表的单词是当前这个点所代表的单词的前缀
- 一个节点的fail指针指向的字符串是当前这个点所代表的单词的最长的后缀
然后众所周知前缀的后缀就是字串,因此我们可以得出一个暴力做法:
对于\(y\)字符串所代表的每个点,在fail树上暴力向上跳,如果遇到\(x\)字符串的结尾就增加答案
然后我们冷静分析一下,暴力向上肯定不好处理,假设我们把所有\(y\)字符串的点都打上标记,然后看看\(x\)字符串的子树里有多少个标记点也是可行的
那么再进一步,问题变成怎样每次只把\(y\)字符串里的点打好标记呢?很简单,只要离线一下就好了
考虑我们重新模拟建立Trie树的过程,这样我们肯定是在按序遍历所有的字符串。同时我们将询问按\(y\)排序,每次处理所有\(y\)相同的询问
然后每次向下走的时候就把当前点点权\(+1\),向上走就\(-1\)撤销,然后相当于维护\(x\)的子树内的权值和
直接DFS序+树状数组即可,总复杂度\(O(26n+n\log n)\)
#include
#include
#include
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
struct ques
{
int x,y,id;
friend inline bool operator < (const ques& A,const ques& B)
{
return A.y