CodeForces 587F Duff is Mad

洛谷传送门

CF 传送门

CF547E 略难的字符串好题。

思路

首先令 m=i=1n|si|

ai 为第 i 个字符串在 AC 自动机上的终止结点。考虑在 AC 自动机上匹配的过程,xy 中出现的次数就相当于在 Trie 树上 ay 到根结点的链上,每个结点都不断跳 fail,有多少个结点是 ax,也就是在 fail 树上,有多少个结点在 ax 的子树内。

如果你在做 CF547E,想到这一步就结束了。但这题求的是 sl...rsk 中的出现次数,即 Trie 树上 ak 到根结点链上的每个结点,在 al...rfail 子树内的出现次数,显然不能直接暴力处理。考虑根号分治,设一个阈值 B

|sk|>B 时,不难发现满足此要求的 kO(mB) 级别的。因此每个 k 可以 O(m) 处理。具体地,处理每个 k 都将 ak 到根结点的链上的点的 size 设为 1,再一遍 dfs 求出子树和,那么询问 (l,r,k) 的答案即为 i=lrsizeai,前缀和预处理后 O(1) 回答每个询问。这部分的时间复杂度为 O(m2B)

|sk|B 时,这意味着每个询问可以 O(|sk|) 处理。设一个 val 值,将每个询问 (l,r,k) 拆成 (1,r,k)(1,l1,k),然后遍历每个字符串,设当前遍历到 i,就将 aifail 树上的子树的 val 加一,处理右端点为 i 的询问时,就直接暴力遍历 Trie 树上 ak 到根结点的链,累加所有结点的 val。我们需要一个支持区间加,单点查的数据结构。因为单点查的次数是 O(qB) 级别的,所以使用区间加 O(m)、单点查 O(1) 的分块则这部分复杂度最优,为 O(nm+qB)

总时间复杂度为 O(nm+m2B+qB)。要使 max(m2B,qB) 最小化,显然在函数图像上取交点最优,所以 B=mq,此时总时间复杂度为 O(nm+mq)

代码

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;
}
posted @   zltzlt  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示