字符串杂题

Trick

  1. 对于子串问题,考虑转化成后缀的前缀或者前缀的后缀进行处理。(A task for substrings)

题目

[OOI 2023] A task for substrings

询问 \(T\) 的子串是不好处理的,考虑变成前缀或者后缀问题。

对于一个询问 \([l,r]\),考虑找到一个串 \(S_i\),记它匹配到 \(T\) 上的区间是 \([L,R]\),满足 \(L\in[1,l),R\in[l,r]\),且 \(R\) 最大。

那么所有匹配结束位置在 \((R,r]\) 的串都在 \([l,r]\) 内,可以直接算答案,剩下的只需要考虑串 \(S_i\) 的子串。

也就是对于串 \(S_i\) 的后缀的一个前缀,要满足匹配的左端点要大于等于 \(l\),对这样的串计数。

这些都可以用 AC 自动机完成。

对于找 \(S_i\),可以对所有 \(S\) 建立 AC 自动机,然后在 \(T\) 上匹配,在线段树上维护出每个匹配结束位置的最小左端点,查寻 \(S_i\) 就可以线段树上二分。

在对所有 \(S_i\) 的反串建立 AC 自动机,然后对每个 \(S\) 的后缀进行匹配,计算一个前缀和即可。

实现很麻烦,稍微抄了下题解。

代码
#include <bits/stdc++.h>

using namespace std;
using ll = long long;

const int N = 1e6 + 10, M = 5e6 + 10, inf = 1e9;

int n, m;
string s[N], t;
int lent, len[N], sum[N];

int tot;
int fail[N], tr[26][N], val[N], id[N];
int rev[M];
ll pre[M], ans[M];

vector< int> G[N];

void init() {
    for ( int i = 0; i <= tot; i ++) {
        fail[i] = val[i] = id[i] = 0;
        G[i].clear();

        for ( int j = 0; j < 26; j ++)
            tr[j][i] = 0;
    }
    tot = 0;
}

void insert( string s, int id, int op) {
    int u = 0;

    if (! op)
        for ( int i = 1; i <= len[id]; i ++) {
            int & v = tr[s[i] - 'a'][u];

            if (! v) v = ++ tot;
            u = v;
        }
    else
        for ( int i = len[id]; i; i --) {
            int & v = tr[s[i] - 'a'][u];

            if (! v) v = ++ tot;
            u = v;
        }        

    val[u] = 1, :: id[u] = id;
}

void build() {
    queue< int> q;

    for ( int i = 0; i < 26; i ++)
        if (tr[i][0]) q.push(tr[i][0]);

    while (q.size()) {
        int u = q.front(); q.pop();
        
        for ( int i = 0; i < 26; i ++) {
            int & v = tr[i][u];

            if (v) {
                fail[v] = tr[i][fail[u]];
                q.push(v);
            } else v = tr[i][fail[u]];
        }
    }

    for ( int i = 1; i <= tot; i ++)
        G[fail[i]].push_back(i);
}

void dfs( int u, int sum, int id) {
    if (! :: id[u]) :: id[u] = id;
    val[u] += sum;
    for ( auto v : G[u]) dfs(v, val[u], :: id[u]);
}

struct sgt {
    #define ls k << 1
    #define rs k << 1 | 1
    #define mid ((l + r) >> 1)

    int mi[M * 4];

    void build( int k = 1, int l = 1, int r = lent) {
        if (l == r) return mi[k] = l - len[rev[l]] + 1, void();
        build(ls, l, mid), build(rs, mid + 1, r);
        mi[k] = min(mi[ls], mi[rs]);
    }

    int find( int x, int y, int v, int k = 1, int l = 1, int r = lent) {
        if (l > y || r < x || mi[k] >= v) return 0;
        if (l == r) return l;

        int res = find(x, y, v, rs, mid + 1, r);
        if (! res) res = find(x, y, v, ls, l, mid);
        return res;

    }

    #undef ls
    #undef rs
    #undef mid
} T;

signed main() {
    ios :: sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    cin >> n >> m;

    cin >> t; t = " " + t;
    lent = (int)t.size() - 1;
    
    for ( int i = 1; i <= n; i ++) {
        cin >> s[i]; len[i] = (int)s[i].size(), sum[i] = sum[i - 1] + len[i];
        s[i] = " " + s[i];
        insert(s[i], i, 0);
    }

    build();
    dfs(0, 0, 0);

    int u = 0;
    for ( int i = 1; i <= lent; i ++) {
        u = tr[t[i] - 'a'][u];
        rev[i] = id[u], pre[i] = pre[i - 1] + val[u];
    }

    T.build();

    init();
    for ( int i = 1; i <= n; i ++)
        insert(s[i], i, 1);

    build(), dfs(0, 0, 0);

    for ( int o = 1; o <= n; o ++) {
        int u = 0;
        for ( int i = len[o]; i; i --) {
            int id = sum[o] - i + 1;
            if (i < len[o]) ans[id] = ans[id - 1];

            u = tr[s[o][i] - 'a'][u];
            ans[id] += val[u];
        }
    }

    while (m --) {
        int l, r; cin >> l >> r;
        int pos = T.find(l, r, l);

        if (! pos) {
            cout << pre[r] - pre[l - 1] << ' ';
        } else {
            cout << pre[r] - pre[pos] + ans[sum[rev[pos] - 1] + pos - l + 1] << ' ';
        }
    }

    return 0;
}
posted @ 2025-11-07 18:53  咚咚的锵  阅读(2)  评论(0)    收藏  举报