AC自动机合集

hdu 2222  http://acm.hdu.edu.cn/showproblem.php?pid=2222

裸的ac自动机,注意找到一个字串时,要顺着它的fail边一直往上找,直到到达根节点为止。


hdu 6208   http://acm.hdu.edu.cn/showproblem.php?pid=6208

给你n个字符串,问是否有一个字符串包含了其余的所有的n - 1个字符串。显然答案只可能为长度最大的字符串,将n个字符串建成ac自动机后,直接用长度最大的字符串进行模式匹配(如果有多个随便取哪一个都行),若成功匹配了所有字符串则存在。


ACM竞赛高校联盟训练赛 第11场(暨大连海事大学校赛)C  https://nanti.jisuanke.com/t/25689

给你n个字符串,求每个字符串在其余n - 1个字符串中出现的次数之和。这道题不能像之前那样对每个串都进行一遍模式匹配,会超时。这里需要用到一个叫做fail树的东西,将ac自动机的所有fail边反向,即可得到一棵树,这棵树就叫做fail树。根据fail边的特性,显然一个结点会且仅会被它的所有子节点所包含,那么只需对fail树进行一遍dfs统计出子树和就可以得出结果。注意一个字符串可能会被另一个字符串包含多次,所以在建trie树插入一个新的字符串时,每达到一个节点,该节点的次数就要加1,而不是像通常那样只在最后的那个节点加1。


AC自动机还可以用于优化动态规划,主要依据的原理是trie树中的节点很少,可以大幅的缩小状态数,而且利用fail边, 很容易找出状态与状态之间的转移关系。

poj 2778    http://poj.org/problem?id=2778

很经典的一道题。我们可以很容易写出一个状态压缩的动态规划,用f[i][s]表示长度位i最后9位状态位s的合法字符串数目,s是一个4进制数,每次从末尾添加一个新的字符进行转移,可以用矩阵乘法进行优化。可惜状态数高达4的9次方,一次矩阵乘法的复杂度就高达4的27次方,肯定超时。其实这种状态表示方法存在大量的冗余状态,我们可以用ac自动机来减小状态的数目。对m个字符串建立ac自动机,我们用trie树中的每个节点表示一个状态,这样状态数最做也就200个,可以接受。

具体是什么意思呢?用f[i][s]表示长度为i其后缀为trie树中的s节点所对应的字符串的合法字符串的数目(若存在多个取最长的)。还是每次从末尾新增一个字符,利用ac自动机我们可以转移到一个新的状态(也就是另一个节点)。我们需要预处理出哪些节点是合法节点,1.首先一个新插入的字符串所对应的节点是非法节点,2.若一个结点在trie 树中的某个父亲节点是非法节点,那么该节点是非法节点,3.若某个节点顺着其fail边往上跑,能跑到一个非法节点,那么该节点也是非法节点。第一类节点在建立trie树时即可找出,第二类和第三类节点很容易在建立fail边时找出。除去这三类点,其余的点都是合法节点,还是用矩阵优化,但在建立状态转移矩阵是我们只允许从一个合法节点转移到另一个合法节点。

#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#define MOD 100000
#define N 200
using namespace std;
typedef long long LL;
LL n, f[N][N], g[N][N], t[N][N];
int m, tot, ch[N][4], fail[N], v[N];
char s[20];

void insert(char *s)
{
	int x = 0;
	for(int i = 0; i < strlen(s); i ++)
	{
		if (s[i] == 'C') s[i] = 'B';
		if (s[i] == 'G') s[i] = 'C';
		if (s[i] == 'T') s[i] = 'D';
		int k = s[i] - 'A';
		if (!ch[x][k]) ch[x][k] = ++ tot;
		x = ch[x][k];
	}
	v[x] = 1;
}

void getfail()
{
	queue<int> q;
	for(int i = 0; i < 4; i ++)
	{
		int k = ch[0][i];
		if (k) 
		{
			fail[k] = 0;
			q.push(k);
		}
	}
	while(!q.empty())
	{
		int x = q.front(); q.pop();
		for(int i = 0; i < 4; i ++)
		{
			int k = ch[x][i];
			if (!k) continue;
			q.push(k);
			int u = fail[x];
			while(u && !ch[u][i]) u = fail[u];
			fail[k] = ch[u][i];
		  if (v[x] || v[fail[k]]) v[k] = 1;
		}
	}
}

void build()
{
	for(int i = 0; i <= tot; i ++)
	  if (!v[i])
	  {
	  	for(int j = 0; j < 4; j ++)
	  	{
	  		int x = i;
	  		while (x && !ch[x][j]) x = fail[x];
	  		x = ch[x][j];
	  		if (!v[x]) f[i][x] ++;
	  	}
	  }
}

void multi(LL n)
{
	if (n == 1)
	{
		for(int i = 0; i <= tot; i ++)
			for(int j = 0; j <= tot; j ++)
				g[i][j] = f[i][j];
		return;
	}
	multi(n / 2);
	memset(t, 0, sizeof(t));
	for(int i = 0; i <= tot; i ++)
		for(int j = 0; j <= tot; j ++)
			for(int k = 0; k <= tot; k ++)
				t[i][j] = (t[i][j] + g[i][k] * g[k][j]) % MOD;
	if (n % 2 == 1)
	{
		memset(g, 0, sizeof(g));
	  for(int i = 0; i <= tot; i ++)
		  for(int j = 0; j <= tot; j ++)
			  for(int k = 0; k <= tot; k ++)
			  	g[i][j] = (g[i][j] + t[i][k] * f[k][j]) % MOD;
	}
	else
		for(int i = 0; i <= tot; i ++)
		  for(int j = 0; j <= tot; j ++)
			  	g[i][j] = t[i][j];
}



int main()
{
	//freopen("1.in", "r", stdin);
	//freopen("1.out", "w", stdout);
  while(cin >> m >> n)
  {
  	tot = 0;
  	memset(v, 0, sizeof(v));
  	memset(ch, 0, sizeof(ch));
  	for(int i = 1; i <= m; i ++)
  	{
  		cin >> s;
  		insert(s);
    }
    memset(fail, 0, sizeof(fail));
    getfail();
    memset(f, 0, sizeof(f));
    build();
    multi(n);
    LL ans = 0;
/*    for(int i = 0; i <= tot; i ++)
    {
    	for(int j = 0; j <= tot; j ++)
    		cout << i << ' ' << j << ' ' << f[i][j] << endl;
    }*/
    for(int i = 0; i <= tot; i ++) ans = (ans + g[0][i]) % MOD;
    cout << ans << endl;
  }
  return 0;
}






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值