洛谷P5840 [COCI2015]Divljak 题解
题意:
Alice 有 $n$ 个字符串 ${S}_1, {S}_2, \ldots, {S}_n$,Bob 有一个字符串集合 ${T}$,一开始集合是空的。
接下来会发生 $q$ 个操作,操作有两种形式:
1 P:Bob 往自己的集合里添加了一个字符串 ${P}$。2 x:Alice 询问 Bob,集合 ${T}$ 中有多少个字符串包含串 ${S}_x$(我们称串 ${A}$ 包含串 ${B}$,当且仅当 ${B}$ 是 ${A}$ 的子串)。输入格式:
第一行一个整数 $n$。
接下来 $n$ 行,第 $i$ 行一个字符串 ${S}_i$。
接下来一行一个整数 $q$。
接下来 $q$ 行,每行一个操作。
输出格式:
对每个
2 x操作,一行一个整数,表示答案。数据范围:
$1 \leq n,q \leq 10^5$,字符串由小写字母构成,$S$ 和 $P$ 的总长分别 $\le 2 \times 10^6$。
这题显然跟多模式串匹配有关,发现最好建 AC 自动姬的就是 $S_1,S_2,\cdots,S_n$ 了,尝试以这个方向入手。
考察在 $T$ 中加入一个新的串 $P$ 会对 $S$ 产生怎样的贡献,记 $r_1,r_2,\cdots,r_l$ 为 $P$ 在 AC 自动姬上走过的节点
容易发现,$r_i$ 到 Fail Tree 根节点路径上的每个节点(所代表的串)都是 $P$ 的子串。
因此所有得到 $P$ 贡献的节点就是 $r_i$ 到根节点路径的节点的并,问题就转化为了树上求并。
树上求并可以通过以下办法计算:先将 $r_i$ 按 Fail Tree 的 dfn 序排序,然后我们做一个树上差分。
具体地,将每个 $r_i$ 增加 $1$ ,然后将 $\operatorname{LCA}(r_i,r_{i+1})$ 增加 $-1$ 。这个可以轻松地用树状数组维护。
时间复杂度 $\mathcal{O}(\sum |S_i| \log \sum |S_i| + \sum |P_i|)$
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)(2e6 + 15))
namespace Trie
{
queue<int> q; signed tot = 1,fail[N],ed[N],trie[N][26];
void insert(char *s)
{
int u = 1; static int tim = 0;
for(int i = 1, c; s[i]; i++)
{
c = s[i] - 'a';
if(!trie[u][c]) trie[u][c] = ++tot;
u = trie[u][c];
}
ed[++tim] = u;
}
void build()
{
fail[1] = 0; q.push(1);
for(int i = 0; i < 26; i++) trie[0][i] = 1;
while(!q.empty())
{
int u = q.front(); q.pop();
for(int i = 0; i < 26; i++)
{
if(trie[u][i])
{
fail[trie[u][i]] = trie[fail[u]][i];
q.push(trie[u][i]);
}else trie[u][i] = trie[fail[u]][i];
}
}
}
}using namespace Trie;
char s[N]; vector<int> vec[N];
int n,Q,r[N],dfn[N],sz[N],dep[N],fa[N],son[N],tree[N],top[N];
void dfs(int u,int f,int d)
{
fa[u] = f; dep[u] = d + 1; sz[u] = 1; int mx = -1;
for(int v : vec[u])
{
if(v == f) continue;
dfs(v,u,d+1); sz[u] += sz[v];
if(sz[v] > mx) { mx = sz[v], son[u] = v; }
}
}
void dfs(int u,int ftop)
{
static int tim = 0; dfn[u] = ++tim; top[u] = ftop;
if(son[u]) dfs(son[u], ftop);
for(int v : vec[u]) {
if(v != fa[u] && v != son[u]) dfs(v,v);
}
}
int lca(int x,int y)
{
while(top[x] != top[y])
{
if(dep[top[x]] < dep[top[y]]) swap(x,y);
x = fa[top[x]];
}
return dep[x] < dep[y] ? x : y;
}
#define lowbit(x) ((x) & (-(x)))
void add(int x,int v) { for(int i = x; i && i <= tot; i += lowbit(i)) tree[i] += v; }
int sum(int x) { int res = 0; for(int i = x; i; i -= lowbit(i)) res += tree[i]; return res; }
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
cin >> n;
for(int i = 1; i <= n; i++) { cin >> (s + 1); insert(s); }
build(); cin >> Q;
for(int i = 2; i <= tot; i++) vec[fail[i]].push_back(i);
dfs(1,0,1); dfs(1,1);
// for(int i = 1; i <= tot; i++) cout << fail[i] << " \n"[i == tot];
for(int op,t,p,l; Q--; )
{
if(cin >> op, op == 1)
{
cin >> (s + 1); p = 1; l = strlen(s + 1);
for(int i = 1, c; i <= l; i++) {
c = s[i] - 'a'; p = trie[p][c]; r[i] = p;
}
// for(int i = 1; i <= l; i++) cout << r[i] << ' ';
sort(r + 1, r + 1 + l, [](int a,int b){ return dfn[a] < dfn[b]; });
for(int i = 1; i <= l; i++) add(dfn[r[i]], 1);
for(int i = 1; i < l; i++) add(dfn[lca(r[i], r[i + 1])], -1);
}else
{
cin >> t; p = ed[t];
// cout << dfn[p] + sz[p] - 1 << ' ' << dfn[p] - 1 << '\n';
cout << (sum(dfn[p] + sz[p] - 1) - sum(dfn[p] - 1)) << '\n';
}
}
return 0;
}
花絮:(本来想去玩把 ow2 的然后调了半天不想玩了)
调这个代码的时候出了一些奇怪的bug,比如两次运行结果不一样或者一些不输出什么的
一般导致这种的有几种情况:变量没赋初值(属于 UB 范畴)、数组越界、RE。
然后我实际上是出现了数组越界的问题,结果不知道什么玄学原因,反正有些东西都没法输出。
因为我是用文件输入来调试的,所以建议这种时候改成标准IO,然后把同步流开回来,不然可能会有玄学的情况
参考文献: