「一题多解」多模匹配(AC自动机 / 暴力)

引子

难道模版题也可以一题多解?答案是:当然可以。
只要仔细分析题目性质并揣测出题人造数据的方法,就能用简单的暴力通过模版题。

但是,切记一点,正式比赛时为了准确性和速度,千万不要使用非主流解法。


题目链接

本人 A C 的是洛谷的 A C 自动机模版。
【模板】AC自动机(简单版)


解法一(384 ms)

AC自动机直接上。
具体可以参考这篇博客

代码

本人写的是指针版。

// Template of Aho-Corasick Automaton (Deterministic Finite Automaton)
#include <cstdio>
#include <cstring>
const int maxn = 1000005;
const int maxm = 26;
struct node {
    int cnt;
    node *fail, *next[26];
    void init() {
        cnt = 0, fail = NULL;
        memset(next, NULL, sizeof(next));
    }
} nodes[maxn], *que[maxn];
struct dfa {
    int e;
    node *root;
    node* _add() {
        nodes[e].init();
        return &nodes[e++];
    }
    void init() {
        e = 0;
        root = _add();
    }
    void insert(char *s) {
        node *u = root;
        for (int i = 0, ch; s[i]; i++) {
            ch = s[i] - 'a';
            if (u -> next[ch] == NULL) {
                u -> next[ch] = _add();
            }
            u = u -> next[ch]; 
        }
        u -> cnt++;
    }
    void getfail() {
        root -> fail = root;
        int h = 0, t = 0;
        node *u;
        for (int i = 0; i < maxm; i++) {
            if (root -> next[i]) {
                root -> next[i] -> fail = root;
                que[t++] = root -> next[i];
            } else {
                root -> next[i] = root;
            }
        }
        while (h < t) {
            u = que[h++];
            for (int i = 0; i < maxm; i++) {
                if (u -> next[i]) {
                    u -> next[i] -> fail = u -> fail -> next[i];
                    que[t++] = u -> next[i];
                } else {
                    u -> next[i] = u -> fail -> next[i];
                }
            }
        }
    }
    int match(char *s) {
        int ch, ans = 0;
        node *u = root, *tmp;
        for (int i = 0; s[i]; i++) {
            ch = s[i] - 'a';
            tmp = u = u -> next[ch];
            while (tmp != root && tmp -> cnt != -1) {
                ans += tmp -> cnt;
                tmp -> cnt = -1;
                tmp = tmp -> fail;
            }
        }
        return ans;
    }
} ac;
int n;
char s[maxn];
int main() {
    ac.init();
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%s", s);
        ac.insert(s);
    }
    ac.getfail();
    scanf("%s", s);
    printf("%d\n", ac.match(s));
    return 0;
}

解法二(180 ms)

记模式串的数量为 n l e n i = m

让我们先想想怎么用暴力代替 K M P n = 1 )。
算出模式串的 H a s h 值,再与被匹配串的对应长度的一段一一比较。时间复杂度 O ( m )

那么,为什么在多模匹配时这个套路不好使了呢?因为模式串太多了,无法一一与被匹配串比较(这样时间复杂度就变成 O ( n m ) 了)。

于是,我们就要找到这些串的共同点,然后分批比较。

我们发现,这些串的长度最多有 O ( m ) 种。
按串长度分类,对于每种长度,暴力将被匹配串扫一遍,用哈希表维护字符串被那些串匹配了。

时间复杂度 O ( m m )

代码
// The Code is Very Ugly
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef unsigned long long ull;
const int maxn = 1000003;
const int base = 27;
char s[maxn];
int n, m, ans;
ull mhash[maxn], power[maxn];
struct word {
    int len;
    ull hash;
    void gethash() {
        for (len = 1; s[len]; len++) {
            hash = hash * base + s[len] - 'a' + 1;
        }
        len--;
    }
    inline bool operator<(const word &o) const{
        return len < o.len;
    }
} w[maxn];
struct hashmap {
    int cnt, tmp, lnk[maxn];
    int nxt[maxn], val[maxn];
    ull key[maxn];
    inline int find(const ull &x) {
        for (int i = lnk[x % maxn]; i; i = nxt[i]) {
            if (key[i] == x) {
                return i;
            }
        }
        return 0;
    }
    inline int& get(const ull &x) {
        return val[find(x)];
    }
    inline void add(const ull &x) {
        tmp = find(x);
        if (tmp) {
            val[tmp]++;
        } else {
            key[++cnt] = x, val[cnt] = 1;
            nxt[cnt] = lnk[x % maxn], lnk[x % maxn] = cnt;
        }
    }
} ump;
void getmhash() {
    m = strlen(s + 1);
    power[0] = 1;
    for (int i = 1; i <= m; i++) {
        power[i] = power[i - 1] * base;
        mhash[i] = mhash[i - 1] * base + s[i] - 'a' + 1;
    }
}
inline ull hashcode(const int &l, const int &r) {
    return mhash[r] - mhash[l - 1] * power[r - l + 1];
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%s", s + 1);
        w[i].gethash();
    }
    sort(w + 1, w + n + 1);
    scanf("%s", s + 1);
    getmhash();
    for (int l, r, i = 1, j = 1; i <= n; i = j + 1, j = i) {
        ump.add(w[i].hash);
        while (w[i].len == w[j + 1].len) {
            ump.add(w[++j].hash);
        }
        for (l = 1, r = w[i].len; r <= m; l++, r++) {
            ans += ump.get(hashcode(l, r));
            ump.get(hashcode(l, r)) = 0;
        }
        for (int k = i; k <= j; k++) {
            ump.get(w[k].hash) = 0;
        }
    }
    printf("%d\n", ans);
    return 0;
}
分析

大家可能会好奇:为什么暴力不但通过了全部数据,而且比正解还快?

让我们来设想一下,出题人是如何造数据的。

模式串要多,否则会被 K M P 乱搞过去。
这样每个模式串的长度又太小了。再加几个长一点的模式串吧。

数据就这么造好了。

解法二是基于不同长度的模式串个数的,而数据中模式串的不同长度个数显然非常小,根本无法卡掉解法二。

再加上正解的复杂度常数较大(因为有 26 个字母),相比之下暴力就显得更快了。

猜你喜欢

转载自blog.csdn.net/weixin_42068627/article/details/80793204