CF587F

题面

f(s,t) 表示串 st 中出现的次数。

首先把询问 i=lrf(si,sk) 拆成 i=1rf(si,sk)i=1l1f(si,sk),然后考虑如何算 i=1lf(si,sk)

建出 AC 自动机和 fail 树,现在有两种方式:(trie 树和 fail 树点集相同,边集不同)

  • 在 trie 树上把 sk 经过的节点都 +1,然后枚举 s1sl 在 fail 树上的子树,统计子树上权值和。
  • 先把 s1sl 在 fail 树上的子树都 +1,然后统计 sk 在 trie 树上经过的节点的权值和。

发现时间瓶颈在于 sk 的长度,又因为 i=1n|si|105,考虑离线根号分治。

设阈值为 B,长度 B 的串用第二种,否则用第一种预处理。

具体实现上,对长度 B 的询问,我们把它拆开、离线、按 l 排序。加子树的操作可以转化为在 dfs 序上的区间加,询问是单点查值,树状数组即可;对长度 >B 的串,同样是把 trie 上的点拍到 dfs 序上,加完之后做前缀和,然后再枚举 l,做原顺序的前缀和。

假设 n,q,i=1n|si| 同阶,复杂度 O(n2B+Bnlogn),当 B=\sqrt\dfrac n{\log n} 时最小,最小为 O(nnlogn)

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;
}
posted @   untitled0  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示