參考:
《算法競賽入門經典-訓練指南》
http://codeforces.com/blog/entry/14854
kuang bin blog
加上不存在的邊,並壓縮冗餘轉移
這個改進《訓練指南》中說過, 本文的目的是總結並實現一個自用的模板。。
AC自動機每個節點有一個 fail 指針, 作用與KMP中相同。
假如我們的節點是這樣的
// link 就是 fail 指針
const int MaxNode = 500000 + 50;
const int CharSet = 26;
int go[MaxNode][CharSet], link[MaxNode];
int val[MaxNode]; // 附加信息,例如判斷是否爲一個單詞的結尾
int nodes;
在AC自動機中輸入一個新的字符c,轉移時會先沿着link指針走,遇到 go[c] not equal 0 的節點 或者到了根就會停下。
中間走的這一段都不存在c轉移,在構造的時候已經知道這個事實了,所以可以把這一段多跑的路給壓縮掉。
把原來不存在的轉移都補上
就再也沒有while了~~
void build() {
queue<int> q;
q.push(0);
while ( !q.empty() ) {
int u = q.front(); q.pop();
int fa = link[u];
for (int i = 0; i < CharSet; ++i) {
int v = go[u][i];
if ( v ) {
link[v] = u ? go[fa][i] : 0;
q.push(v);
} else {
go[u][i] = go[fa][i];
}
}
}
}
init();
ac_insert("hers");
ac_insert("she");
ac_insert("he");
ac_insert("s");
get_draw(root, "");
ac_construct();
match("she");
return 0;
改進後fail指針的指向
一開始寫的是指針形式的,也是預先分配內存
struct Node {
Node *go[CharSet], *link;
int count;
};
不過指針的開銷貌似會更大? 雖然看起來是一樣的。。
hdu 2222 一直 MLE
所以實際的板子改成了開數組的
用last指針進一步提高轉移效率
用last指針表示沿着link走,遇到的第一個爲某個單詞結尾的節點。
這樣在match的時候,沿着 last 走而不是 link,遇到的都是單詞節點
void build() {
queue<int> q;
q.push(0);
while ( !q.empty() ) {
int u = q.front(); q.pop();
int fa = link[u];
for (int i = 0; i < CharSet; ++i) {
int v = go[u][i];
if ( v ) {
link[v] = u ? go[fa][i] : 0;
last[v] = val[link[v]] ? link[v] : last[link[v]];
q.push(v);
} else {
go[u][i] = go[fa][i];
}
}
}
}
我的模板
namespace Trie {
const int MaxNode = 500000 + 50;
const int CharSet = 26;
int go[MaxNode][CharSet], link[MaxNode], last[MaxNode];
int val[MaxNode];
int nodes;
void init() {
nodes = 1;
memset(go[0], 0, sizeof(go[0]));
}
int allocate() {
memset(go[nodes], 0, sizeof(go[nodes]));
val[nodes] = 0;
last[nodes] = 0;
link[nodes ++] = 0;
return nodes - 1;
}
void add( const char* s ) {
int cur = 0;
for (; *s; ++ s) {
int id = *s - 'a';
if ( !go[cur][id] ) {
go[cur][id] = allocate();
}
cur = go[cur][id];
}
val[cur] += 1;
}
int match(const char* s) {
int cur = 0, ret = 0;
for (; *s; ++s) {
cur = go[cur][*s-'a'];
int tmp = cur;
if ( val[tmp] ) {
ret += val[tmp], val[tmp] = 0;
}
while ( last[tmp] ) {
tmp = last[tmp];
if ( val[tmp] ) {
ret += val[tmp], val[tmp] = 0;
}
}
}
return ret;
}
void build() {
queue<int> q;
q.push(0);
while ( !q.empty() ) {
int u = q.front(); q.pop();
int fa = link[u];
for (int i = 0; i < CharSet; ++i) {
int v = go[u][i];
if ( v ) {
link[v] = u ? go[fa][i] : 0;
last[v] = val[link[v]] ? link[v] : last[link[v]];
q.push(v);
} else {
go[u][i] = go[fa][i];
}
}
}
}
}
構造不包含模式的串
當 自動機 構造好後,我們有了 r 個狀態。那麼就存在一個 r x r的矩陣 T,T(i,j) 表示狀態 i 轉移到狀態 j 的方法數
T 可以這樣構造
// 輸入 (當前狀態,輸入)
// 返回轉移到的狀態, -1 表示無效轉移
int check(int st, int input) {
st= go[st][input];
// 如果當前節點或其後綴是模式串,則爲無效轉移
if ( tag[st] || last[st] ) return -1;
return st;
}
Mat T(r); // r 爲狀態總數
memset(T.m, 0, sizeof(T.m));
for (int i = 0; i < n; ++i) {
for (int j = 0; j < CharSet; ++j) {
int st = check(i, j);
if ( st != -1 ) T.m[i][st] += 1;
}
}
稱不包含模式的串爲合法串
矩陣
例題
POJ 2778 DNA Sequence
詳解可以看這篇