引子
难道模版题也可以一题多解?答案是:当然可以。
只要仔细分析题目性质并揣测出题人造数据的方法,就能用简单的暴力通过模版题。
但是,切记一点,正式比赛时为了准确性和速度,千万不要使用非主流解法。
题目链接
本人
的是洛谷的
自动机模版。
【模板】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)
记模式串的数量为 , 。
让我们先想想怎么用暴力代替
(
)。
算出模式串的
值,再与被匹配串的对应长度的一段一一比较。时间复杂度
那么,为什么在多模匹配时这个套路不好使了呢?因为模式串太多了,无法一一与被匹配串比较(这样时间复杂度就变成 了)。
于是,我们就要找到这些串的共同点,然后分批比较。
我们发现,这些串的长度最多有
种。
按串长度分类,对于每种长度,暴力将被匹配串扫一遍,用哈希表维护字符串被那些串匹配了。
时间复杂度 。
代码
// 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;
}
分析
大家可能会好奇:为什么暴力不但通过了全部数据,而且比正解还快?
让我们来设想一下,出题人是如何造数据的。
模式串要多,否则会被 乱搞过去。
这样每个模式串的长度又太小了。再加几个长一点的模式串吧。
数据就这么造好了。
解法二是基于不同长度的模式串个数的,而数据中模式串的不同长度个数显然非常小,根本无法卡掉解法二。
再加上正解的复杂度常数较大(因为有 个字母),相比之下暴力就显得更快了。