ac状态机以及后缀树
作者:互联网
看提交记录(有注释)
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m;
int tr[N][4], dar[N], idx;
int q[N], ne[N];
char str[N];
int f[N][N];
int get(char c)
{
if (c == 'A') return 0;
if (c == 'T') return 1;
if (c == 'G') return 2;
return 3;
}
void insert() //插入字符串,是建立一般的树的过程
{
int p = 0;
for (int i = 0; str[i]; i ++ )
{
int t = get(str[i]);
if (tr[p][t] == 0) tr[p][t] = ++ idx; //这里必须从1开始,因为初始点是0,是空的root
p = tr[p][t];
}
dar[p] = 1; //标记该点是dargerous
}
void build() //将树完整化,变成后缀树,也就是后缀自动机,每个点代表一个状态,每条边代表一种方式
{
int hh = 0, tt = -1; //用队列实现
for (int i = 0; i < 4; i ++ )
if (tr[0][i])
q[ ++ tt] = tr[0][i]; //初始化队头
while (hh <= tt) //完整化树,按照层建立,因为是要按照字符多少便利的,层数就是字符多少
{
int t = q[hh ++ ];
for (int i = 0; i < 4; i ++ )
{
int p = tr[t][i]; //子节点(一个待转移的状态)
if (!p) tr[t][i] = tr[ne[t]][i]; //如果是空的,就代表要进行完善了,把它的最长后缀变成它的子节点
else //如果不空
{
ne[p] = tr[ne[t]][i]; //更新该点的fail值
q[ ++ tt] = p; //入队
dar[p] |= dar[ne[p]]; //如果有后缀是dar,自己也是dar
}
}
}
}
int main()
{
int T = 1;
while (scanf("%d", &n), n)
{
memset(tr, 0, sizeof tr);
memset(dar, 0, sizeof dar);
memset(ne, 0, sizeof ne);
idx = 0;
for (int i = 0; i < n; i ++ )
{
scanf("%s", str);
insert();
}
build();
scanf("%s", str + 1);
m = strlen(str + 1); //字符串从1开始
memset(f, 0x3f, sizeof f);
f[0][0] = 0; //修改到第i个字符,当前在状态机中是j时的最小需要修改次数
for (int i = 0; i < m; i ++ )
for (int j = 0; j <= idx; j ++ ) //枚举各个状态
for (int k = 0; k < 4; k ++ ) //枚举到下一个状态的转移途径
{
int t = get(str[i + 1]) != k; //未匹配
int p = tr[j][k]; //可能是fail[], 也可能不是,这里对树进行了修改,使得下面就是fail
if (!dar[p]) f[i + 1][p] = min(f[i + 1][p], f[i][j] + t); //下个状态可以达到
}
int res = 0x3f3f3f3f;
for (int i = 0; i <= idx; i ++ ) res = min(res, f[m][i]);
if (res == 0x3f3f3f3f) res = -1;
printf("Case %d: %d\n", T ++, res);
}
return 0;
}
标签:ac,return,后缀,tr,++,状态机,int,str,include 来源: https://www.cnblogs.com/sherkevin/p/16390561.html