AC自动机/fail树相关(模板)

AC自动机:
last[i] : 表示 i 这个节点跳fail指针最近单词结尾. 这个优化异常快.
f[i]: 表示 i 的fail(失配指针)指向的点, 它是尽量具有相同后缀的点, 也就是其父亲的fail指针的下方是否有匹配点, 有就指过去,否则指向根.

const int maxn = 5e5+5;
int ch[maxn][26], cnt;
int val[maxn], f[maxn], last[maxn];
struct Ac {
    int sz, clen = 26;
    Ac() {
        sz = 0; Fill(val, 0);
        Fill(ch[0], 0);
    }
    int idx(char c) { return c - 'a'; }
    void Insert(char *s, int v) {
        int u = 0, n = strlen(s);
        for (int i = 0 ; i < n ; i ++) {
            int c = idx(s[i]);
            if (!ch[u][c]) {
                ++sz; Fill(ch[sz], 0);
                val[sz] = 0;  // 清零的, 如果是赋值的可以不用.
                ch[u][c] = sz;
            }
            u = ch[u][c];
        }
        val[u] += v; //字符串的字符的附加信息
    }
    void getFail() {
        queue<int> q;
        f[0] = 0;  // fail指针, 以及last优化.
        for (int c = 0; c < clen ; c ++) {
            int u = ch[0][c];
            if (u) {
                f[u] = 0; q.push(u);
                last[u] = 0;
            }
        }
        while (!q.empty()) {
            int r = q.front(); q.pop();
            for (int c = 0 ; c < clen ; c ++) {
                int u = ch[r][c];
                if (!u) continue;
                q.push(u);
                int v = f[r];
                while (v && !ch[v][c]) v = f[v];
                f[u] = ch[v][c];
                last[u] = val[f[u]] ? f[u] : last[f[u]];
            }
        }
    }
    void cal(int j) {
        while (j) {
            cnt += val[j];
            val[j] = 0;
            j = last[j];
        }
    }
    void Find(char *T) {
        int n = strlen(T);
        int j = 0;
        for (int i = 0 ; i < n ; i ++) {
            int c = idx(T[i]);
            while (j && !ch[j][c]) j = f[j];
            j = ch[j][c];
            if (val[j]) cal(j);
            else if (last[j]) cal(last[j]);
        }
    }
};
char s[maxn], t[maxn<<1];
void solve() {
    int n; scanf("%d", &n);
    Ac ac;
    for (int i = 1 ; i <= n ; i ++) {
        scanf("%s", s);
        ac.Insert(s, 1);
    }
    scanf("%s", t); ac.getFail();
    cnt = 0; ac.Find(t);
    printf("%d\n", cnt);
}

faiil树(有树了以后就可以把相关的树算法套上去)
(大部分都是根据AC自动机改的)
bzoj - 3172
题目大意: 给定n个串, 问每个串在这n个串中一共出现了多少次. (包括自身的这个串)

const int maxn = 1e6+5;
int sz, ch[maxn][26], f[maxn];
int pos[maxn], val[maxn], siz[maxn];
int fa[maxn];
int tid, p1[maxn], p2[maxn];
vector<int>g[maxn];
struct FailTree {
    int clen;
    FailTree() {
        sz = tid = 0; Fill(ch[0], 0);
        Fill(val, 0); clen = 26;
    }
    int idx(char c) { return c - 'a'; }
    void Insert(char *s, int v) {
        int u = 0, n = strlen(s);
        for (int i = 0 ; i < n ; i ++) {
            int c = idx(s[i]);
            if (!ch[u][c]) {
                ++sz; Fill(ch[sz], 0);
                ch[u][c] = sz;
            }
            u = ch[u][c]; val[u]++;
        }
        pos[v] = u;
    }
    void getFail() {
        queue<int> q; f[0] = 0;
        for (int c = 0 ; c < clen ; c ++) {
            int u = ch[0][c];
            if (u) {
                f[u] = 0; q.push(u);
            }
        }
        while (!q.empty()) {
            int r = q.front(); q.pop();
            for (int c = 0 ; c < clen ; c ++) {
                int u = ch[r][c];
                if (!u) continue;
                q.push(u);
                int v = f[r];
                while (v && !ch[v][c]) v = f[v];
                f[u] = ch[v][c];
            }
        } // 建图
        for (int i = 1 ; i <= sz ; i ++) {
            g[f[i]].pb(i);
        }
        dfs(0);
    }
    void dfs(int u) {
        p1[u] = ++tid; siz[u] = val[u];
        for (int i = 0 ; i < sz(g[u]) ; i ++) {
            dfs(g[u][i]);
            siz[u] += siz[g[u][i]];
        }
        p2[u] = tid;
    }
    void work(int n) {
        for (int i = 1 ; i <= n ; i ++) {
            printf("%d\n", siz[pos[i]]);
        }
    }
};
char s[maxn];
void solve() {
    int n; scanf("%d", &n);
    FailTree T;
    for (int i = 1 ; i <= n ; i ++) {
        scanf("%s", s);
        T.Insert(s, i);
    }
    T.getFail(); T.work(n);
}

大概说说fail树.
我们建好AC自动机以后, 每个节点都有一个fail指针, 我们将fail指针进行反向可以发现这就是一棵树, 它有什么用了? 我们如果暴力的算一个串a在串b中出现了多少次. 那么是不是a一定出现在b串的某个前缀的后缀上, 也就是你对b串的每一个位置跳fail指针如果能跳到a的标记点, 那么ans++, 最后ans就是统计结果. 这样是不是很暴力? 其实这样我们可以发现是一种多对一的形态, 多次我们反向后就是一对多, 如果b串的某一个位置能够跳到a串, 那么反向后是不是一定在a为根的子树内, 也就是如果把b串每一个位置都加一, 实际上就是统计以a为根的子树内的和是多少… 这样我们就可以用dfs序, 将子树转化为区间问题, 就可以用线段树或者树状数组维护了…

###提示:

做题发现last优化非常给力, 每次暴力的跳last统计次数, 居然不比fail树慢… 所以在很多题中如果要在fail 树 套上很多数据结构写起来巨麻烦的时候可以试试用last优化的暴力, 说不定有惊喜了… 典型例题HDU-4117

题目总结:

发现如果是字符串统计方案数一类的题目, 基本上都是AC自动机 + dp, 而且dp方程很难写, 基本就是属于比较难的一类题了, 如果是不同要求(可重叠或者不可重叠)的统计字符串在某个字符串出现的次数之类的, 一般在last优化暴力跳, 或者简单fail树处理下, 以及很简单的dp递推, 这一类的题目就是属于中档题, 比较好写把… 目前我就只能做这类题目. 统计方案数的真好难QAQ…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值