Luogu P3966 [TJOI2013]单词

本文深入探讨了AC自动机的实现原理及其在处理字符串匹配问题中的应用,包括如何构建Fail树并进行DP计算,同时介绍了后缀数组结合ST表和倍增法在求解字符串后缀问题中的高效解决方案。
题目链接 \(Click\) \(Here\)

本题\(AC\)自动机写法的正解之一是\(Fail\)树上跑\(DP\)

\(AC\)自动机是\(Trie\)树和\(Fail\)树共存的结构,前者可以方便地处理前缀问题,而在后者中,一个节点的子节点,代表以当前字符串为后缀的所有字符串节点(根节点外向\(Fail\)树)。我们最初给每个串的所有前缀计数\(+1\),后期统计时,在该前缀的所有后缀上(\(Fail\)树上的祖先节点上),将其自身答案累加上去,就是总共出现的次数。

#include <bits/stdc++.h>
using namespace std;

const int N = 1000010;

struct AC_Auto {
    int top, sta[N];
    long long sum[N];
    int cnt, ch[N][26], pre[N], fail[N];

    AC_Auto () {
        cnt = top = 0;
        memset (ch, 0, sizeof (ch));
        memset (sum, 0, sizeof (sum));
        memset (pre, 0, sizeof (pre));
        memset (fail, 0, sizeof (fail));
    }

    void add_str (char *s) {
        int l = strlen (s), now = 0;
        for (int i = 0; i < l; ++i) {
            if (!ch[now][s[i] - 'a']) {
                ch[now][s[i] - 'a'] = ++cnt;
            }
            pre[ch[now][s[i] - 'a']] = now;
            now = ch[now][s[i] - 'a'];
            sum[now]++;
        }
        sta[++top] = now;
    }

    queue <int> q;
    
    void build () {
        for (int i = 0; i < 26; ++i) {
            if (ch[0][i]) {
                q.push (ch[0][i]);
            }
        }
        while (!q.empty ()) {
            int u = q.front (); q.pop ();
            for (int i = 0; i < 26; ++i) {
                if (ch[u][i]) {
                    q.push (ch[u][i]);
                    fail[ch[u][i]] = ch[fail[u]][i];
                } else {
                    ch[u][i] = ch[fail[u]][i];
                }
            }
        }
    }

    int _cnt, head[N];

    struct edge {
        int nxt, to;
    }e[N];

    void add_edge (int from, int to) {
        e[++_cnt].nxt = head[from];
        e[_cnt].to = to;
        head[from] = _cnt;
    } 

    void dp (int u) {
        for (int i = head[u]; ~i; i = e[i].nxt) {
            dp (e[i].to);
            sum[u] += sum[e[i].to];
        }
    }
    
    void get_ans () {
        _cnt = 0;
        memset (head, -1, sizeof (head));
        for (int i = 1; i <= cnt; ++i) {
            add_edge (fail[i], i);
        }
        dp (0);
        for (int i = 1; i <= top; ++i) {
            cout << sum[sta[i]] << endl;
        }
    }
    
    
    
    
}AC;

int n; char s[N];

int main () {
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        scanf ("%s", s);
        AC.add_str (s);
    }
    AC.build ();
    AC.get_ans ();
}

\(UPD:\)新增了后缀数组\(+ST\)\(+\)倍增写法

#include <bits/stdc++.h>
using namespace std;

const int N = 2000010;

char tmp[N];
int n, len, ban = 'z', s[N], id[N], sa[N], tp[N], rk[N], _rk[N], bin[N], _len[N], height[N];

void get_height (int n) {
    int k = 0;
    for (int i = 1; i <= n; ++i) {
        if (k != 0) --k;
        int j = sa[rk[i] - 1];
        while (s[i + k] == s[j + k]) ++k;
        height[rk[i]] = k;
    }
}

void base_sort (int n, int m) {
    for (int i = 0; i <= m; ++i) bin[i] = 0;
    for (int i = 1; i <= n; ++i) bin[rk[tp[i]]]++;
    for (int i = 1; i <= m; ++i) bin[i] += bin[i - 1];
    for (int i = n; i >= 1; --i) sa[bin[rk[tp[i]]]--] = tp[i];
}

void suffix_sort (int n, int m) {
    for (int i = 1; i <= n; ++i) {
        tp[i] = i, rk[i] = s[i];
    }
    base_sort (n, m);
    for (int w = 1; w <= n; w <<= 1) {
        int cnt = 0;
        for (int i = n - w + 1; i <= n; ++i) tp[++cnt] = i;
        for (int i = 1; i <= n; ++i) if (sa[i] > w) tp[++cnt] = sa[i] - w;
        base_sort (n, m);
        memcpy (_rk, rk, sizeof (rk));
        rk[sa[1]] = cnt = 1;
        for (int i = 2; i <= n; ++i) {
            rk[sa[i]] = (_rk[sa[i - 1]] == _rk[sa[i]]) && (_rk[sa[i - 1] + w] == _rk[sa[i] + w]) ? cnt : ++cnt; 
        }
        if (cnt == n) break;
        m = cnt;
    }
}

int fa[N][25];

int query (int l, int r) {
    if (l >= r) return 0; l++;
    int mx = log2 (r - l + 1);
    return min (fa[l][mx], fa[r - (1 << mx) + 1][mx]);
}

void get_STlist (int n) {
    int mx = log2 (n);
    for (int i = 1; i <= n; ++i) fa[i][0] = height[i];
    for (int i = 1; i <= mx; ++i) {
        for (int j = 1; j <= n - (1 << i) + 1; ++j) {
            fa[j][i] = min (fa[j][i - 1], fa[j + (1 << (i - 1))][i - 1]);
        }
    }
}

int main () {
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        scanf ("%s", tmp);
        int l = strlen (tmp);
        id[i] = len + 1;
        _len[i] = l;
        for (int j = 0; j < l; ++j) {
            s[++len] = tmp[j];
        }
        s[++len] = ++ban;
    }
    suffix_sort (len, ban);
    get_height (len);
    get_STlist (len);
    for (int i = 1; i <= n; ++i) {
        int l = rk[id[i]], r = rk[id[i]];
        for (int j = 24; j >= 0; --j) {
            int tol = l - (1 << j);
            int tor = r + (1 << j);
            if (tol >= 1   && query (tol, rk[id[i]]) >= _len[i]) l = tol;
            if (tor <= len && query (rk[id[i]], tor) >= _len[i]) r = tor;
        }
        printf ("%d\n", r - l + 1);
    }
}

转载于:https://www.cnblogs.com/maomao9173/p/10468748.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值