CodeForces 587F Duff is Mad
比 CF547E 略难的字符串好题。
思路
首先令 。
设 为第 个字符串在 AC 自动机上的终止结点。考虑在 AC 自动机上匹配的过程, 在 中出现的次数就相当于在 Trie 树上 到根结点的链上,每个结点都不断跳 ,有多少个结点是 ,也就是在 树上,有多少个结点在 的子树内。
如果你在做 CF547E,想到这一步就结束了。但这题求的是 在 中的出现次数,即 Trie 树上 到根结点链上的每个结点,在 的 子树内的出现次数,显然不能直接暴力处理。考虑根号分治,设一个阈值 。
当 时,不难发现满足此要求的 是 级别的。因此每个 可以 处理。具体地,处理每个 都将 到根结点的链上的点的 设为 ,再一遍 dfs 求出子树和,那么询问 的答案即为 ,前缀和预处理后 回答每个询问。这部分的时间复杂度为 。
当 时,这意味着每个询问可以 处理。设一个 值,将每个询问 拆成 ,然后遍历每个字符串,设当前遍历到 ,就将 在 树上的子树的 加一,处理右端点为 的询问时,就直接暴力遍历 Trie 树上 到根结点的链,累加所有结点的 。我们需要一个支持区间加,单点查的数据结构。因为单点查的次数是 级别的,所以使用区间加 、单点查 的分块则这部分复杂度最优,为 。
总时间复杂度为 。要使 最小化,显然在函数图像上取交点最优,所以 ,此时总时间复杂度为 。
代码
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; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】