作为一名菜鸡选手,我的目的仅仅是初步了解ac自动机的原理和各种性质。。。
AC自动机的前置技能点 KMP,字典树
ac自动机的原理解释可以参考
hihocode hiho一下
我解释不清楚
1。跟后缀自动机不一样,ac自动机,是一张有向有环图,后缀自动机是有向无环图。
2。ac自动机是多对一的多模式串匹配。
3。ac自动机的常用的属性有 下面几个
1.val[i] 记录编号为i的节点的性质,一般用于记录,是否是一个模式串的终点
2.last[i] 记录编号为i的节点后缀链接,既 如果last[i] 不为 0 说明编号last[i]的节点为一个模式串,且是编号为i的字符串的最长后缀。(跟后缀自动机的后缀链接是一个东西)
3. fail[i] fail指针,跟kmp一样 表示编号为i的节点失配后,应该转移到哪一个状态。
4. ch[i][j] 状态转移方程 表示 编号为i的节点 下一个字符接上j的话,应该匹配到哪一个状态。
建立字典树。
对于val数组 ,一般在建字典树的时候就要处理完成。 对于ch数组,在建字典树的时候会‘’初步‘’处理完成。*这时候ch表示的是一颗树
构建fail指针
原理,我讲了也没(wo)人)(ye)听(bu)的(hui)懂。
在构建玩fail指针后,会构建出上述的常用属性。并且会将字典树补成一张有向有环图。
这个时候ch已经不再是字典树当中的那种定义了,ch[i][j]它表示编号为i的节点能匹配到的最优位置。
然后,就可以结合题意 ,运用ac自动机的属性来解决问题了。
AC自动机的题目,其实就是对trie图的属性运用。
下面,列出一些题目,介绍一下ac自动机的常见用法。
1. hdu2222
ac自动机模板题,随便都能过,唯一可以注意的是,因为题目要求的是求字符串匹配,所以暴力往回跳的时候,可以不用fail指针往回跳,直接使用last既可,复杂度O(len * N) 但实际上不可能有这么多,
#include
#ifdef LOCAL
#define debug(x) cout<<#x<<" = "<<(x)<
#else
#define debug(x) 1;
#endif
#define lson id<<1,l,mid
#define rson id<<1|1,mid+1,r
#define lowbit(x) x&-x
#define mp make_pair
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int MOD = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const ll INFLL = 0x3f3f3f3f3f3f3f3fll;
const int MAXN = 2e6 + 5;
const int maxn = 2e6 + 100;
const int SIGMA_SIZE = 26;
const int MAXNODE = 100000 * 50;
const int MAXS = 150 + 10;
struct ACautomata {
int ch[MAXNODE][SIGMA_SIZE];
int f[MAXNODE]; // fail函数
int val[MAXNODE]; // 每个字符串的结尾结点都有一个非0的val
int last[MAXNODE]; // 输出链表的下一个结点
int sz;
int d[MAXNODE];
void init() {
sz = 1;
memset (ch[0], 0, sizeof (ch[0]) );
memset(d, 0, sizeof(d));
}
// 字符c的编号
inline int idx (char c) {
return c - 'a';
}
// 插入字符串。v必须非0
void insert (char *s) {
int u = 0, n = strlen (s);
for (int i = 0; i < n; i++) {
int c = idx (s[i]);
//printf("%c", s[i]);
if (!ch[u][c]) {
memset (ch[sz], 0, sizeof (ch[sz]) );
val[sz] = 0;
ch[u][c] = sz++;
}
u = ch[u][c];
//printf("%d", u);
//puts("");
}
val[u] += 1;
}
// 递归打印匹配文本串str[i]结尾的后缀,以结点j结尾的所有字符串
void print (int i, int j) {
if (j) {
print (i, last[j]);
}
}
// 在T中找模板
void find (char* T) {
int n = strlen (T);
int j = 0; // 当前结点编号,初始为根结点
for (int i = 0; i < n; i++) { // 文本串当前指针
int c = idx (T[i]);
j = ch[j][c];
if (val[j]) print (i, j);
else if (last[j]) print (i, last[j]); // 找到了!
}
}
// 计算fail函数
void getFail() {
queue<int> q;
f[0] = 0;
// 初始化队列
for (int c = 0; c < SIGMA_SIZE; c++) {
int u = ch[0][c];
if (u) {
f[u] = 0;
q.push (u);
last[u] = 0;
}
}
// 按BFS顺序计算fail
while (!q.empty() ) {
int r = q.front();
q.pop();
for (int c = 0; c < SIGMA_SIZE; c++) {
int u = ch[r][c];
if (!u) {
ch[r][c] = ch[f[r]][c];
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]];
}
}
}
int query(char *s) {
int len =strlen(s);
int now = 0;
int ans =0;
for(int i=0; i'a' ];
int tmp = now;
while(now) {
if(val[now]) {
ans+=val[now];
val[now]=0;
}
now = last[now];
}
now =tmp;
}
return ans;
}
} ac;
char s[MAXN];
int main() {
int n;
int T;
cin>>T;
while(T--) {
cin >> n;
ac.init();
for (int i = 1; i <= n; i++) {
scanf ("%s", s);
ac.insert (s);
}
ac.getFail();
scanf ("%s", s );
//ac.find(s);
printf("%d\n",ac.query(s));
}
return 0;
}
2.
HDU - 2896
同样的模板题,可以注意的地方跟hdu2222 是一样的。
#include
#ifdef LOCAL
#define debug(x) cout<<#x<<" = "<<(x)<
#else
#define debug(x) 1;
#endif
#define lson id<<1,l,mid
#define rson id<<1|1,mid+1,r
#define lowbit(x) x&-x
#define mp make_pair
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int MOD = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const ll INFLL = 0x3f3f3f3f3f3f3f3fll;
const int MAXN = 1e5;
const int maxn = 2e6 + 100;
const int SIGMA_SIZE = 130;
const int MAXNODE = 1e5+10;
const int MAXS = 150 + 10;
struct ACautomata {
int ch[MAXNODE][SIGMA_SIZE];
int f[MAXNODE]; // fail函数
int val[MAXNODE]; // 每个字符串的结尾结点都有一个非0的val
int last[MAXNODE]; // 输出链表的下一个结点
int sz;
//int d[MAXNODE];
void init() {
sz = 1;
memset (ch[0], 0, sizeof (ch[0]) );
//memset(d, 0, sizeof(d));
}
// 字符c的编号
inline int idx (char c) {
return (int)c;
}
// 插入字符串。v必须非0
void insert (char *s,int ids) {
int u = 0, n = strlen (s);
for (int i = 0; i < n; i++) {
int c = idx (s[i]);
//printf("%c", s[i]);
if (!ch[u][c]) {
memset (ch[sz], 0, sizeof (ch[sz]) );
val[sz] = 0;
ch[u][c] = sz++;
}
u = ch[u][c];
}
val[u] =ids;
}
// 递归打印匹配文本串str[i]结尾的后缀,以结点j结尾的所有字符串
void print (int i, int j) {
if (j) {
print (i, last[j]);
}
}
// 在T中找模板
void find (char* T) {
int n = strlen (T);
int j = 0; // 当前结点编号,初始为根结点
for (int i = 0; i < n; i++) { // 文本串当前指针
int c = idx (T[i]);
j = ch[j][c];
if (val[j]) print (i, j);
else if (last[j]) print (i, last[j]); // 找到了!
}
}
// 计算fail函数
void getFail() {
queue<int> q;
f[0] = 0;
// 初始化队列
for (int c = 0; c < SIGMA_SIZE; c++) {
int u = ch[0][c];
if (u) {
f[u] = 0;
q.push (u);
last[u] = 0;
}
}
// 按BFS顺序计算fail
while (!q.empty() ) {
int r = q.front();
q.pop();
for (int c = 0; c < SIGMA_SIZE; c++) {
int u = ch[r][c];
if (!u) {
ch[r][c] = ch[f[r]][c];
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 query(char *s,vector<int> &V) {
V.clear();
int len =strlen(s);
int now = 0;
int ans =0;
for(int i=0; iint )s[i]];
int tmp = now;
while(now) {
if(val[now]) {
V.push_back(val[now]);
}
now = f[now];
}
now =tmp;
}
}
} ac;
char s[MAXN];
int main() {
int n;
while(cin>>n) {
ac.init();
for (int i = 1; i <= n; i++) {
scanf ("%s", s);
ac.insert (s,i);
}
ac.getFail();
cin>>n;
int tot=0;
vector<int> V;
for(int i=0;iscanf ("%s",s);
ac.query(s,V);
if(V.size()){
sort(V.begin(),V.end());
V.erase(unique(V.begin(),V.end()),V.end());
tot++;
printf("web %d:",i+1);
for(int i=0;iprintf(" %d",V[i]);
}
puts("");
}
}
printf("total: %d\n",tot);
}
return 0;
}
3.
HDU-3065
模板题
#include
#ifdef LOCAL
#define debug(x) cout<<#x<<" = "<<(x)<
#else
#define debug(x) 1;
#endif
#define lson id<<1,l,mid
#define rson id<<1|1,mid+1,r
#define lowbit(x) x&-x
#define mp make_pair
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int MOD = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const ll INFLL = 0x3f3f3f3f3f3f3f3fll;
const int MAXN = 1e5;
const int maxn = 2e6 + 100;
const int SIGMA_SIZE = 130;
const int MAXNODE =101000;
const int MAXS = 1005;
char str[1005][100];
char s[2000005];
int ans[10000];
struct ACautomata {
int ch[MAXNODE][SIGMA_SIZE];
int f[MAXNODE]; // fail函数
int val[MAXNODE]; // 每个字符串的结尾结点都有一个非0的val
int last[MAXNODE]; // 输出链表的下一个结点
int sz;
//int d[MAXNODE];
void init() {
sz = 1;
memset (ch[0], 0, sizeof (ch[0]) );
memset(ans,0,sizeof ans);
//memset(d, 0, sizeof(d));
}
// 字符c的编号
inline int idx (char c) {
return (int)c;
}
// 插入字符串。v必须非0
void insert (char *s,int ids) {
int u = 0, n = strlen (s);
for (int i = 0; i < n; i++) {
int c = idx (s[i]);
//printf("%c", s[i]);
if (!ch[u][c]) {
memset (ch[sz], 0, sizeof (ch[sz]) );
val[sz] = 0;
ch[u][c] = sz++;
}
u = ch[u][c];
}
val[u] =ids;
}
// 递归打印匹配文本串str[i]结尾的后缀,以结点j结尾的所有字符串
void print (int i, int j) {
if (j) {
print (i, last[j]);
}
}
// 在T中找模板
void find (char* T) {
int n = strlen (T);
int j = 0; // 当前结点编号,初始为根结点
for (int i = 0; i < n; i++) { // 文本串当前指针
int c = idx (T[i]);
j = ch[j][c];
if (val[j]) print (i, j);
else if (last[j]) print (i, last[j]); // 找到了!
}
}
// 计算fail函数
void getFail() {
queue<int> q;
f[0] = 0;
// 初始化队列
for (int c = 0; c < SIGMA_SIZE; c++) {
int u = ch[0][c];
if (u) {
f[u] = 0;
q.push (u);
last[u] = 0;
}
}
// 按BFS顺序计算fail
while (!q.empty() ) {
int r = q.front();
q.pop();
for (int c = 0; c < SIGMA_SIZE; c++) {
int u = ch[r][c];
if (!u) {
ch[r][c] = ch[f[r]][c];
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 query(char *s) {
int len =strlen(s);
int now = 0;
for(int i=0; iint tmp = now;
while(now) {
if(val[now]) {
ans[val[now]]++;
}
now = last[now];
}
now =tmp;
}
}
} ac;
int main() {
int n;
while(cin>>n) {
ac.init();
for (int i = 1; i <= n; i++) {
scanf ("%s", str[i]);
ac.insert (str[i],i);
}
ac.getFail();
scanf("%s",s);
ac.query(s);
for(int i=1;i<=n;i++){
if(!ans[i]) continue;
printf("%s: %d\n",str[i],ans[i]);
}
}
return 0;
}
4.
poj-2778
这题还是很有意思滴,利用里AC自动机的性质,因为ac自动机是一张图,任意两个点之间右边,就说明他们两个一定有一个是另一个的子串或者是后缀。 利用这个建立邻接矩阵,跑矩阵快速幂即可。
#include
#include
#include
#include
#include
#define lson id<<1,l,mid
#define rson id<<1|1,mid+1,r
#define lowbit(x) x&-x
#define mp make_pair
#define pb push_back
using namespace std;
typedef long long ll;
typedef long long LL;
typedef pair<int, int> pii;
const int MOD = 100000;
const int INF = 0x3f3f3f3f;
const ll INFLL = 0x3f3f3f3f3f3f3f3fll;
const int MAXN = 1e5;
const int maxn = 2e6 + 100;
const int SIGMA_SIZE = 130;
const int MAXNODE =101000;
const int MAXS = 1005;
char str[1005][100];
char s[2000005];
int ans[10000];
int maps[300];
struct ACautomata {
int ch[MAXNODE][SIGMA_SIZE];
int f[MAXNODE]; // fail函数
int val[MAXNODE]; // 每个字符串的结尾结点都有一个非0的val
int last[MAXNODE]; // 输出链表的下一个结点
int sz;
//int d[MAXNODE];
void init() {
sz = 1;
memset (ch[0], 0, sizeof (ch[0]) );
//memset(d, 0, sizeof(d));
}
// 字符c的编号
inline int idx (char c) {
return maps[c];
}
// 插入字符串。v必须非0
void insert (char *s,int ids) {
int u = 0, n = strlen (s);
for (int i = 0; i < n; i++) {
int c = idx (s[i]);
//printf("%c", s[i]);
if (!ch[u][c]) {
memset (ch[sz], 0, sizeof (ch[sz]) );
val[sz] = 0;
ch[u][c] = sz++;
}
u = ch[u][c];
}
val[u] =ids;
}
// 递归打印匹配文本串str[i]结尾的后缀,以结点j结尾的所有字符串
void print (int i, int j) {
if (j) {
print (i, last[j]);
}
}
// 在T中找模板
void find (char* T) {
int n = strlen (T);
int j = 0; // 当前结点编号,初始为根结点
for (int i = 0; i < n; i++) { // 文本串当前指针
int c = idx (T[i]);
j = ch[j][c];
if (val[j]) print (i, j);
else if (last[j]) print (i, last[j]); // 找到了!
}
}
// 计算fail函数
void getFail() {
queue<int> q;
f[0] = 0;
// 初始化队列
for (int c = 0; c < SIGMA_SIZE; c++) {
int u = ch[0][c];
if (u) {
f[u] = 0;
q.push (u);
last[u] = 0;
}
}
// 按BFS顺序计算fail
while (!q.empty() ) {
int r = q.front();
q.pop();
for (int c = 0; c < SIGMA_SIZE; c++) {
int u = ch[r][c];
if (!u) {
ch[r][c] = ch[f[r]][c];
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 query(char *s) {
int len =strlen(s);
int now = 0;
for(int i=0; iint tmp = now;
while(now) {
if(val[now]) {
ans[val[now]]++;
}
now = last[now];
}
now =tmp;
}
}
} ac;
//<---------------------quickpow----------------------->
const int matX = 1e2 + 5;
const int mod = 100000;
struct Matrix {
int n, m, s[matX][matX];
Matrix(int n, int m): n(n), m(n) {
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) s[i][j] = 0;
//s[i][i]=1;
}
}
Matrix operator*(const Matrix &P)const {
Matrix ret(n, P.m);
for(int i = 0; i < n; i++) {
for(int k = 0; k < m; k++) {
if(s[i][k]) {
for(int j = 0; j < P.m; j++) {
ret.s[i][j] = ((LL)s[i][k] * P.s[k][j] + ret.s[i][j]) % mod;
}
}
}
}
return ret;
}
Matrix operator^(const LL &P)const {
LL num = P;
Matrix ret(n, m), tmp = *this;
for(int i = 0; i < n; i++) ret.s[i][i] = 1;
while(num) {
if(num & 1) ret = ret * tmp;
tmp = tmp * tmp;
num >>= 1;
}
return ret;
}
};
int main() {
maps['A']=0;
maps['C']=1;
maps['T']=2;
maps['G']=3;
long long n,m;
while(cin>>n>>m) {
ac.init();
for (int i = 1; i <= n; i++) {
scanf ("%s", str[i]);
ac.insert (str[i],1);
}
ac.getFail();
Matrix E(ac.sz,ac.sz);
for(int i=0; i<=ac.sz; i++) {
if(ac.val[i] || ac.last[i]) {
continue;
}
for(int j=0; j<4; j++) {
//if(!ac.ch[i][j]) continue;
if(ac.val[ac.ch[i][j]] || ac.last[ac.ch[i][j]]) {
continue;
}
E.s[i][ac.ch[i][j]]++;
}
}
E=E^m;
long long ans =0 ;
for(int i=0; i0][i];
ans%=MOD;
}
cout<return 0;
}
5.HDU-2825
这题我觉得也很有意思。
要求长度为n的串中,包含不少于k种给出模式串的方案数。
最多给出m个模式串,m<=10
观察到m很小,应该要很轻松的联想的状压。
然后吧Tire图建出来 , 跑一个简单DP即可。
dp[26][MAXNODE][1050];// dp[i][j][k] 表示 第i个数,到达第j个状态,包含k个子串的方案数
#include
#include
#include
#include
#include
#define lson id<<1,l,mid
#define rson id<<1|1,mid+1,r
#define lowbit(x) x&-x
#define mp make_pair
#define pb push_back
using namespace std;
typedef long long ll;
typedef long long LL;
typedef pair<int, int> pii;
const int MOD = 20090717;
const int INF = 0x3f3f3f3f;
const ll INFLL = 0x3f3f3f3f3f3f3f3fll;
const int SIGMA_SIZE = 30;
const int MAXNODE =255;
const int MAXS = 1005;
char str[1005];
char s[2000005];
int ans[10000];
int maps[300];
struct ACautomata {
int ch[MAXNODE][SIGMA_SIZE];
int f[MAXNODE]; // fail函数
int val[MAXNODE]; // 每个字符串的结尾结点都有一个非0的val
int last[MAXNODE]; // 输出链表的下一个结点
int sz;
//int d[MAXNODE];
void init() {
sz = 1;
memset (ch[0], 0, sizeof (ch[0]) );
//memset(d, 0, sizeof(d));
}
// 字符c的编号
inline int idx (char c) {
return c-'a';
}
// 插入字符串。v必须非0
void insert (char *s,int ids) {
int u = 0, n = strlen (s);
for (int i = 0; i < n; i++) {
int c = idx (s[i]);
//printf("%c", s[i]);
if (!ch[u][c]) {
memset (ch[sz], 0, sizeof (ch[sz]) );
val[sz] = 0;
ch[u][c] = sz++;
}
u = ch[u][c];
}
val[u] =ids;
}
// 递归打印匹配文本串str[i]结尾的后缀,以结点j结尾的所有字符串
void print (int i, int j) {
if (j) {
print (i, last[j]);
}
}
// 在T中找模板
void find (char* T) {
int n = strlen (T);
int j = 0; // 当前结点编号,初始为根结点
for (int i = 0; i < n; i++) { // 文本串当前指针
int c = idx (T[i]);
j = ch[j][c];
if (val[j]) print (i, j);
else if (last[j]) print (i, last[j]); // 找到了!
}
}
// 计算fail函数
void getFail() {
queue<int> q;
f[0] = 0;
// 初始化队列
for (int c = 0; c < SIGMA_SIZE; c++) {
int u = ch[0][c];
if (u) {
f[u] = 0;
q.push (u);
last[u] = 0;
}
}
// 按BFS顺序计算fail
while (!q.empty() ) {
int r = q.front();
q.pop();
for (int c = 0; c < SIGMA_SIZE; c++) {
int u = ch[r][c];
if (!u) {
ch[r][c] = ch[f[r]][c];
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 query(char *s) {
int len =strlen(s);
int now = 0;
for(int i=0; iint tmp = now;
while(now) {
if(val[now]) {
ans[val[now]]++;
}
now = last[now];
}
now =tmp;
}
}
} ac;
int dp[26][MAXNODE][1050];// dp[i][j][k] 表示 第i个数,到达第j个状态,包含k个子串的方案数
int num[2050];
int getsize(int a){
int num=0;
while(a){
num+=a&1;
a>>=1;
}
return num;
}
int main() {
long long n,m,k;
for(int i=0;i<2050;i++){
num[i]=getsize(i);
}
while(cin>>n>>m>>k) {
if(n==0 && m==0 && k==0) break;
ac.init();
for (int i = 1; i <= m; i++) {
scanf ("%s", str);
int lensssss = strlen(str);
if(lensssss >n) continue;
ac.insert (str,i);
}
ac.getFail();
memset(dp,0,sizeof dp);
int now=0;
dp[0][0][0]=1;
int v,tmp,nv;
for(int i=0;ifor(int j=0;jfor(int ks = 0;ks<(1<if(!dp[i][j][ks]) continue;
for(int z=0;z<=25;z++){
v= ac.ch[j][z];
tmp = v;
nv=ks;
while(v){
if(ac.val[v])
nv|=(1<<(ac.val[v]-1));
//cout<
v=ac.last[v];
}
// if(nv==3){
// printf("dp[%d][%d][%d]=%lld\n",i,j,ks,dp[i][j][ks]);
// }
dp[i+1][tmp][nv]+=dp[i][j][ks];
dp[i+1][tmp][nv]%=MOD;
}
}
}
}
int ans =0;
for(int j=0;jfor (int ks=0;ks<(1<if(num[ks]>=k)
ans+=dp[n][j][ks];
ans %= MOD;
}
}
printf("%d\n",ans);
}
return 0;
}
未完待续。。。。。。。。