CodeForces 587F Duff is Mad
作者:互联网
比 CF547E 略难的字符串好题。
思路
首先令 \(m = \sum\limits_{i=1}^n |s_i|\)。
设 \(a_i\) 为第 \(i\) 个字符串在 AC 自动机上的终止结点。考虑在 AC 自动机上匹配的过程,\(x\) 在 \(y\) 中出现的次数就相当于在 Trie 树上 \(a_y\) 到根结点的链上,每个结点都不断跳 \(\mathrm{fail}\),有多少个结点是 \(a_x\),也就是在 \(\mathrm{fail}\) 树上,有多少个结点在 \(a_x\) 的子树内。
如果你在做 CF547E,想到这一步就结束了。但这题求的是 \(s_{l...r}\) 在 \(s_k\) 中的出现次数,即 Trie 树上 \(a_k\) 到根结点链上的每个结点,在 \(a_{l...r}\) 的子树内的出现次数,显然不能直接暴力处理。考虑根号分治,设一个阈值 \(B\)。
当 \(|s_k| > B\) 时,不难发现满足此要求的 \(k\) 是 \(O(\frac{m}{B})\) 级别的。因此每个 \(k\) 可以 \(O(m)\) 处理。具体地,处理每个 \(k\) 都将 \(a_k\) 到根结点的链上的点的 \(\mathrm{size}\) 设为 \(1\),再一遍 dfs 求出子树和,那么询问 \((l,r,k)\) 的答案即为 \(\sum\limits_{i=l}^r \mathrm{size}_{a_i}\),前缀和预处理后 \(O(1)\) 回答每个询问。这部分的时间复杂度为 \(O(\frac{m^2}{B})\)。
当 \(|s_k| \le B\) 时,这意味着每个询问可以 \(O(|s_k|)\) 处理。设一个 \(val\) 值,将每个询问 \((l,r,k)\) 拆成 \((1,r,k) - (1,l-1,k)\),然后遍历每个字符串,设当前遍历到 \(i\),就将 \(a_i\) 在 \(\mathrm{fail}\) 树上的子树的 \(val\) 加一,处理右端点为 \(i\) 的询问时,就直接暴力遍历 Trie 树上 \(a_k\) 到根结点的链,累加所有结点的 \(val\)。我们需要一个支持区间加,单点查的数据结构。因为单点查的次数是 \(O(qB)\) 级别的,所以使用区间加 \(O(\sqrt{m})\)、单点查 \(O(1)\) 的分块则这部分复杂度最优,为 \(O(n \sqrt{m} + qB)\)。
总时间复杂度为 \(O(n \sqrt{m} + \frac{m^2}{B} + qB)\)。要使 \(\max(\frac{m^2}{B},qB)\) 最小化,显然在函数图像上取交点最优,所以 \(B = \frac{m}{\sqrt{q}}\),此时总时间复杂度为 \(O(n \sqrt{m} + m \sqrt{q})\)。
代码
code
/*
p_b_p_b txdy
AThousandMoon txdy
AThousandSuns txdy
hxy txdy
*/
#include <bits/stdc++.h>
#define pb push_back
#define fst first
#define scd second
using namespace std;
typedef long long ll;
typedef pair<ll, ll> pii;
const int maxn = 100100;
int n, m, q, len[maxn], idx[maxn], sz[maxn];
int bel[maxn], block, lb[maxn], rb[maxn];
ll val[maxn], tag[maxn], ans[maxn], sum[maxn];
int B;
int head[maxn], elen;
int st[maxn], times, ed[maxn];
char s[maxn];
struct query {
int l, r, k;
} qq[maxn];
struct edge {
int to, next;
} edges[maxn << 1];
void add_edge(int u, int v) {
edges[++elen].to = v;
edges[elen].next = head[u];
head[u] = elen;
}
struct node {
int op, x, id;
node() {}
node(int a, int b, int c) : op(a), x(b), id(c) {}
};
vector<node> qa[maxn];
bool cmp(node a, node b) {
return a.x < b.x;
}
void update(int l, int r, ll x) {
if (bel[l] == bel[r]) {
for (int i = l; i <= r; ++i) {
val[i] += x;
}
return;
}
for (int i = l; i <= rb[bel[l]]; ++i) {
val[i] += x;
}
for (int i = bel[l] + 1; i < bel[r]; ++i) {
tag[i] += x;
}
for (int i = lb[bel[r]]; i <= r; ++i) {
val[i] += x;
}
}
ll query(int x) {
return val[x] + tag[bel[x]];
}
struct AC {
int fail[maxn], fa[maxn], tot, ch[maxn][26];
void init() {
tot = 0;
memset(fail, 0, sizeof(fail));
memset(fa, 0, sizeof(fa));
memset(ch, 0, sizeof(ch));
}
void insert(char *s, int id) {
int p = 0;
for (int i = 0; s[i]; ++i) {
if (!ch[p][s[i] - 'a']) {
ch[p][s[i] - 'a'] = ++tot;
fa[tot] = p;
}
p = ch[p][s[i] - 'a'];
}
idx[id] = p;
}
void build() {
queue<int> q;
for (int i = 0; i < 26; ++i) {
if (ch[0][i]) {
q.push(ch[0][i]);
}
}
while (q.size()) {
int u = q.front();
q.pop();
for (int i = 0; i < 26; ++i) {
if (ch[u][i]) {
fail[ch[u][i]] = ch[fail[u]][i];
q.push(ch[u][i]);
} else {
ch[u][i] = ch[fail[u]][i];
}
}
}
for (int i = 1; i <= tot; ++i) {
add_edge(fail[i], i);
}
}
void dfs(int u) {
for (int i = head[u]; i; i = edges[i].next) {
int v = edges[i].to;
dfs(v);
sz[u] += sz[v];
}
}
void dfs2(int u) {
st[u] = ++times;
for (int i = head[u]; i; i = edges[i].next) {
int v = edges[i].to;
dfs2(v);
}
ed[u] = times;
}
} ac;
void solve() {
ac.init();
scanf("%d%d", &n, &q);
for (int i = 1; i <= n; ++i) {
scanf("%s", s);
len[i] = strlen(s);
m += len[i];
ac.insert(s, i);
}
ac.build();
B = sqrt(1LL * m * m / q);
for (int i = 1; i <= q; ++i) {
scanf("%d%d%d", &qq[i].l, &qq[i].r, &qq[i].k);
}
for (int i = 1; i <= q; ++i) {
if (len[qq[i].k] > B) {
if (qq[i].l > 1) {
qa[qq[i].k].pb(node(-1, qq[i].l - 1, i));
}
qa[qq[i].k].pb(node(1, qq[i].r, i));
}
}
for (int i = 1; i <= n; ++i) {
if (qa[i].empty()) {
continue;
}
for (int u = idx[i]; u; u = ac.fa[u]) {
sz[u] = 1;
}
ac.dfs(0);
for (int j = 1; j <= n; ++j) {
sum[j] = sum[j - 1] + sz[idx[j]];
}
for (node p : qa[i]) {
ans[p.id] += 1LL * p.op * sum[p.x];
}
qa[i].clear();
for (int u = 0; u <= ac.tot; ++u) {
sz[u] = 0;
}
}
ac.dfs2(0);
block = sqrt(times);
for (int i = 1; i <= times; ++i) {
bel[i] = (i - 1) / block + 1;
}
for (int i = 1; i <= times; ++i) {
if (!lb[bel[i]]) {
lb[bel[i]] = i;
}
rb[bel[i]] = i;
}
for (int i = 1; i <= q; ++i) {
if (len[qq[i].k] <= B) {
if (qq[i].l > 1) {
qa[qq[i].l - 1].pb(node(-1, qq[i].k, i));
}
qa[qq[i].r].pb(node(1, qq[i].k, i));
}
}
for (int i = 1; i <= n; ++i) {
update(st[idx[i]], ed[idx[i]], 1);
for (node p : qa[i]) {
for (int u = idx[p.x]; u; u = ac.fa[u]) {
ans[p.id] += 1LL * p.op * query(st[u]);
}
}
}
for (int i = 1; i <= q; ++i) {
printf("%lld\n", ans[i]);
}
}
int main() {
int T = 1;
// scanf("%d", &T);
while (T--) {
solve();
}
return 0;
}
标签:qq,结点,ch,587F,int,CodeForces,maxn,Mad,fail 来源: https://www.cnblogs.com/zltzlt-blog/p/16436194.html