CF587F
设
首先把询问
建出 AC 自动机和 fail 树,现在有两种方式:(trie 树和 fail 树点集相同,边集不同)
- 在 trie 树上把
经过的节点都 +1,然后枚举 到 在 fail 树上的子树,统计子树上权值和。 - 先把
到 在 fail 树上的子树都 +1,然后统计 在 trie 树上经过的节点的权值和。
发现时间瓶颈在于
设阈值为
具体实现上,对长度
假设
code:
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define endl '\n'
#define rep(i, s, e) for(int i = s; i <= e; ++i)
#define per(i, s, e) for(int i = s; i >= e; --i)
#define F first
#define S second
//#define int ll
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef __int128_t i128;
typedef __uint128_t u128;
typedef pair<int, int> pii;
constexpr int N = 1e5 + 5, B = 100;
int n, Q, tr[N][26], fail[N], m, ed[N], dfn[N], sz[N], pre[N];
string s[N];
bool b[N];
vector<int> to[N];
vector<ll> ss[N];
ll ans[N];
struct Query {
int l, k, id;
Query(int L = 0, int K = 0, int i = 0):
l(L), k(K), id(i) {}
};
vector<Query> q;
#define lb(x) ((x) & -(x))
struct BIT {
int c[N];
void clear() { memset(c, 0, sizeof c); }
void add(int i, int v) {
for(; i <= m; i += lb(i)) c[i] += v;
}
int sum(int i) {
int res = 0;
for(; i; i -= lb(i)) res += c[i];
return res;
}
} bit;
void insert(string &s, int id) { // 插入 trie
int p = 0;
for(auto i : s) {
int c = i - 'a';
if(!tr[p][c]) tr[p][c] = ++m;
p = tr[p][c];
}
ed[id] = p;
}
void build() { // 构建 fail 树
queue<int> q;
rep(i, 0, 25) if(tr[0][i]) q.push(tr[0][i]);
while(!q.empty()) {
int u = q.front(); q.pop();
rep(i, 0, 25) {
if(tr[u][i]) fail[tr[u][i]] = tr[fail[u]][i], q.push(tr[u][i]);
else tr[u][i] = tr[fail[u]][i];
}
}
rep(i, 1, m) to[fail[i]].push_back(i);
}
void dfs(int p) { // 算出 fail 树上的 dfs 序和子树大小
static int ind = 0;
dfn[p] = ind++, sz[p] = 1;
for(auto i : to[p]) dfs(i), sz[p] += sz[i];
}
signed main() {
#ifdef ONLINE_JUDGE
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
#endif
cin >> n >> Q;
rep(i, 1, n) {
cin >> s[i];
insert(s[i], i);
b[i] = s[i].size() > B;
}
build();
dfs(0);
rep(i, 1, n) if(b[i]) { // 预处理长度 >B 的
memset(pre, 0, sizeof pre);
int p = 0;
for(auto j : s[i]) p = tr[p][j - 'a'], ++pre[dfn[p]];
rep(j, 1, m) pre[j] += pre[j - 1];
ss[i].resize(n + 1);
rep(j, 1, n) ss[i][j] = pre[dfn[ed[j]] + sz[ed[j]] - 1] - pre[dfn[ed[j]] - 1] + ss[i][j - 1]; // 前缀和
}
rep(i, 1, Q) {
int l, r, k; cin >> l >> r >> k;
if(b[k]) ans[i] = ss[k][r] - ss[k][l - 1];
else {
q.emplace_back(l - 1, k, -i);
q.emplace_back(r, k, i);
}
}
sort(q.begin(), q.end(), [](Query &a, Query &b){ return a.l < b.l; });
int j = 0;
for(auto i : q) { // 离线处理长度 <B 的
while(j < i.l) {
++j;
bit.add(dfn[ed[j]], 1), bit.add(dfn[ed[j]] + sz[ed[j]], -1);
}
ll res = 0;
int p = 0;
for(auto j : s[i.k]) p = tr[p][j - 'a'], res += bit.sum(dfn[p]);
if(i.id < 0) ans[-i.id] -= res;
else ans[i.id] += res;
}
rep(i, 1, Q) cout << ans[i] << endl;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?