一道比较模板的题,但是比较灵活。
1.先对字符串建立trie树,同时维护每个节点父亲信息fa[now]
。
P表示改模式串结束
B表示回退到父亲节点
2.对trie树建立fail指针&fail树。
3.对fail树处理出dfs序,以便用树状数组维护子树和。
4.读入询问,按y关键字排序。(离线处理)
5.在trie上遍历字符串,
每到一个单词的结尾(‘P’)统一处理出所有询问。(第x个单词在这个单词中出现了几次)
‘B’:撤销上一步操作
‘*’:树状数组改位置+1,继续向下处理。
我的写法相关变量的逻辑关系更乱,我自己也捋了很久。核心代码处有详细注释
Graph中存fail树:dfn为dfs序;sz为子树大小
注意:将这棵树的dfs序求出之后,原来的trie中的结点序号是无任何意义的,后序操作(单点修改+维护子树sum)都是对于dfs序进行操作。
ACAM中存ac自动机:id为第n个单词与其trie中结尾的映射;fa为父亲节点;ne为fail指针。
#include <bits/stdc++.h>
using namespace std;
//-----pre_def----
const double PI = acos(-1.0);
const int INF = 0x3f3f3f3f;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
#define fir(i, a, b) for (int i = (a); i <= (b); i++)
#define rif(i, a, b) for (int i = (a); i >= (b); i--)
#define endl '\n'
#define init_h memset(h, -1, sizeof h), idx = 0;
#define lowbit(x) x &(-x)
//---------------
const int N = 1e5 + 10;
int n, m;
char str[N];
int s[N];
void add(int x, int k)
{
for (int i = x; i < N; i += lowbit(i))
{
s[i] += k;
}
}
int sum(int x)
{
int res = 0;
for (int i = x; i; i -= lowbit(i))
{
res += s[i];
}
return res;
}
int sum(int l, int r)
{
return sum(r) - sum(l - 1);
}
struct Graph
{
int h[N], e[N], ne[N], dfn[N], sz[N], idx, times;
Graph()
{
init_h;
}
void add(int a, int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
void dfs(int u)
{
dfn[u] = ++times;//这步操作之后,u无意义了。
sz[times] = 1;
for (int i = h[u]; ~i; i = ne[i])
{
dfs(e[i]);
sz[dfn[u]] += sz[dfn[e[i]]];
}
}
} G;
struct ACAM
{
int tr[N][26], id[N], fa[N], idx, n, ne[N];
void insert()
{
int now = 0;
for (int i = 0; str[i]; i++)
{
if (str[i] == 'P') //finish
{
id[++n] = now;
}
else if (str[i] == 'B')
{
now = fa[now]; //back
}
else
{
int tmp = str[i] - 'a';
if (!tr[now][tmp])
tr[now][tmp] = ++idx, fa[idx] = now;
now = tr[now][tmp];
}
}
}
void build() //bfs
{
queue<int> q;
fir(i, 0, 25) if (tr[0][i]) q.push(tr[0][i]);
while (q.size())
{
int t = q.front();
q.pop();
fir(i, 0, 25)
{
if (tr[t][i])
{
ne[tr[t][i]] = tr[ne[t]][i];
q.push(tr[t][i]);
}
else
{
tr[t][i] = tr[ne[t]][i];
}
}
}
fir(i, 1, idx)
{
G.add(ne[i], i);
}
}
} ac;
vector<PII> q[N];
int ans[N];
void init() {}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
int StartTime = clock();
#endif
scanf("%s", str);
ac.insert();
ac.build();
scanf("%d", &m);
fir(i, 1, m)
{
int a, b;
scanf("%d%d", &a, &b);
q[b].push_back({a, i});
}
G.dfs(0);
add(G.dfn[0], 1);
int now = 0, nowid = 0;
for (int i = 0; str[i]; i++)
{
if (str[i] == 'P')
{
//q[nowid]
nowid++;
for (auto item : q[nowid])
{
ans[item.second] += sum(G.dfn[ac.id[item.first]], G.dfn[ac.id[item.first]] + G.sz[G.dfn[ac.id[item.first]]] - 1);
//item.second 询问编号
//item.first 第item.first个单词在第nowid个单词中出现了几次
//ac.id[item.first] 第item.first个单词在trie中的结尾点编号
//G.dfn[ac.id[item.first]] 将trie中的点编号转化为fail树中的dfs序号
//G.sz[G.dfn[ac.id[item.first]]] fail树中该点(dfs序)的子树大小
}
}
else if (str[i] == 'B')
{
add(G.dfn[now], -1);
now = ac.fa[now];
}
else
{
now = ac.tr[now][str[i] - 'a'];
add(G.dfn[now], 1);
}
}
fir(i, 1, m) cout << ans[i] << endl;
#ifndef ONLINE_JUDGE
printf("Run_Time = %d ms\n", clock() - StartTime);
#endif
return 0;
}