首页 > 其他分享 >CF587F

CF587F

时间:2023-04-24 19:48:27浏览次数:40  
标签:pre int rep tr CF587F fail sum

题面

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

首先把询问 \(\sum\limits_{i=l}^rf(s_i,s_k)\) 拆成 \(\sum\limits_{i=1}^rf(s_i,s_k)-\sum\limits_{i=1}^{l-1}f(s_i,s_k)\),然后考虑如何算 \(\sum\limits_{i=1}^lf(s_i,s_k)\)。

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

  • 在 trie 树上把 \(s_k\) 经过的节点都 +1,然后枚举 \(s_1\) 到 \(s_l\) 在 fail 树上的子树,统计子树上权值和。
  • 先把 \(s_1\) 到 \(s_l\) 在 fail 树上的子树都 +1,然后统计 \(s_k\) 在 trie 树上经过的节点的权值和。

发现时间瓶颈在于 \(s_k\) 的长度,又因为 \(\sum\limits_{i=1}^n|s_i|\le10^5\),考虑离线根号分治。

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

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

假设 \(n,q,\sum\limits_{i=1}^n|s_i|\) 同阶,复杂度 \(O(\dfrac{n^2}B+Bn\log n)\),当 \(B=\sqrt\dfrac n{\log n}\) 时最小,最小为 \(O(n\sqrt{n\log n})\)。

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;
}

标签:pre,int,rep,tr,CF587F,fail,sum
From: https://www.cnblogs.com/untitled0/p/17350635.html

相关文章