AC自动机(应用)

AC自动机(应用)


【求解有几个模式串在主串中出现过】: HDU 2222 Keywords Search

    遍历Tire树即可,由于只是判断是否出现过,判断过的即可清零可以大大优化速度。

	int Find(char* s)
	{
		int c;
		int now = 0, ans = 0;
		while(*s)
		{
			c = *s - 'a';
			int k = now = ch[now][c];
			while(k)
			{
				if(num[k] == 0) break;//优化
					ans += num[k];
					num[k] = 0;//优化
					k = Fail[k];
			}
			s++;
		}
		return ans;
	}	

【AC自动机+矩阵快速幂求解全集中去除存在给定非法串的固定长度字符串种类】:

POJ 2778 DNA Sequence

    为AC自动机的结点建立邻接矩阵,由于转移边是有限集,非法串已经有了标记(注意,非法串的完全标记无法一步到位必须在BFS过程中把标记下传否则无法规避:AATG中的AT非法串)。若某节点是合法的,那么遍历转移,合法转移邻接矩阵值+1,最后做一次m级矩阵快速幂后,累计第一行的答案即可(字符串从0开始构造的)。


【AC自动机+矩阵快速幂求解全集中去除存在给定非法串的所有长度字符串种类】

HDU 2243 考研路茫茫——单词情结

    和上一题大同小异,但是由于长度是所有不超过上限的,我们应该对矩阵进行改造,扩展一维度的纵列全是1用了来求和,这样应该多算一次方,且结果会多1,减去即可求出不包含给定字符串的种类,利用全集数量即可间接求出所有包含的字符串的数量,不过全集的计算也应该设计一个矩阵,然后利用快速幂计算:

[ 26 0 1 1 ] \begin{bmatrix}26&0\\1&1 \end{bmatrix} [26101]

全集数为: M a t r i x [ 0 ] [ 0 ] + M a r t i x [ 1 ] [ 0 ] − 1 Matrix[0][0]+Martix[1][0] - 1 Matrix[0][0]+Martix[1][0]1


【AC自动机+状态压缩dp求解固定长度出现给定模式串至少K次的方案数】:

HDU 2825 Wireless Password

  d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] 表示匹配到第 i i i个字符保存自动机恰好位于 j j j结点且出现字符串集合为 k k k的情况,第三维为状态压缩维。

//#include
#include
#include
#include
#include
#include
#include
#include
#include
#define lowb(x) ((x) & (-x))
using namespace std;
const int N = 10 + 10;
const int M = 2e6 + 10;
const int MAX_N = 100 + 10;
const int SIZE = 26;
const int INF_int = 1e7;
const long long  P = 20090717;
const double EPS = 1e-8;
const long long INF_ll = 1e14;
typedef  long long ll;
typedef unsigned long long ull;

struct Aco_Corasick_automaton{
	int Fail[MAX_N];//失败转移点
	int ch[MAX_N][SIZE];//转移点
	int num[MAX_N];//前缀数
	int cnt;//Tire树的结点数
//初始化
	void init()
	{
		for(int i = 0; i < MAX_N; ++i)
		{
			Fail[i] = 0;
			num[i] = 0;
			for(int j = 0; j < SIZE; ++j)
				ch[i][j] = 0;
			
		}
		cnt = 0;
	}
//构建Tire树
	void Build_Tire(char* s, int x)
	{
		int now = 0, c;
		while(*s)
		{
			c = *s - 'a'; //根据字符集定义
			if(ch[now][c] == 0) ch[now][c] = ++cnt;
			now = ch[now][c];
			s++;
		}
		num[now] |= (1 << x);
	}
//构建失败转移点
	void Build_Fail()
	{
		queue<int> q;
		for(int i = 0;i < SIZE; ++i)
		{
			if(ch[0][i])
			{
				q.push(ch[0][i]);
				Fail[ch[0][i]] = 0;
			}
		}
		while(q.size())
		{
			int now = q.front(); q.pop();
			num[now] |= num[Fail[now]];
			for(int i = 0; i < SIZE; ++i)
			{
				if(ch[now][i] == 0) ch[now][i] = ch[Fail[now]][i];
				else
				{
					q.push(ch[now][i]);
					Fail[ch[now][i]] = ch[Fail[now]][i];
				}
			}
		}
	}

}AC;
int n, m, k, dp[30][MAX_N][1 << 11],cal[1 << 11];
char s[N];
int main()
{ 	
	for(int i = 1; i < (1 << 11); ++i)
	{
		int now = 0;
		for(int j = 0; j < 11; ++j)
			if(i & (1 << j)) now++;
		cal[i] = now;
	}
	while(scanf("%d%d%d", &n, &m, &k) != EOF)
	{
		if(n == 0) break;
		getchar();
		AC.init();
		for(int i = 1; i <= m; ++i)
		{
			scanf("%s", s);
			getchar();
			AC.Build_Tire(s, i - 1);
		}
		AC.Build_Fail();
		for(int i = 0; i <= n; ++i)
			for(int j = 0; j < MAX_N; ++j)
				for(int k = 0; k < (1 << m); ++k)
					dp[i][j][k] = 0;
		dp[0][0][0] = 1;
		for(int i = 0; i <= n; ++i)
		{
			for(int j = 0; j <= AC.cnt; ++j)
			{
				for(int k = 0; k < (1 << m); ++k)
				{
					if(dp[i][j][k] == 0) continue;
					for(int p = 0; p < 26; ++p)
					{
						int ni = i + 1;
						int nj = AC.ch[j][p];
						int nk = k | AC.num[nj];
						dp[ni][nj][nk] += dp[i][j][k];
						dp[ni][nj][nk] %= P;
					}
					
				}
			}
		}
		int ans = 0;
		for(int i = 0; i < (1 << m); ++i)
		{
			int now = cal[i];	
			if(now < k) continue;		
			for(int j = 0; j <= AC.cnt; ++j)
			{
				
				ans += dp[n][j][i];
				ans %= P;
			}
		}
		printf("%d\n", ans);
	}
 
	return 0;
}   

【AC自动机加速DP】: UVA - 1401 - Remember the Word

修改一下模板,把集合串存入AC自动机,暴力匹配即可。

		for(int i = 1; i <= len; ++i)
		{
		
			cur = AC.ch[cur][T[i - 1] - 'a'];
			int k = cur;
			dp[i] = 0;
			while(k)
			{
				if(AC.num[k])
				{
				dp[i] += dp[i - AC.Len[k]];
				dp[i] %= 20071027;
				}
				k = AC.Fail[k];
			}	
		}

【Tire性质求解】:UVA - 11732 - “strcmp()” Anyone?

主要是理解题意:次数是 s t r c m p ( ) strcmp() strcmp()函数的比较调用次数,包括了s[i]==t[i]s[i]==t[i]

S S S T T T完全相同:比较次数为 ( s t r l e n ( S ) + 1 ) ∗ 2 (strlen(S)+1)*2 (strlen(S)+1)2

S S S T T T不完全相同:比较次数 s t r l e n ( 公 共 前 缀 ) ∗ 2 + 1 strlen(公共前缀)*2+1 strlen()2+1

边插入Tire边计算即可。


【AC自动机的Tire树dp】UVA -11468 - Substring

该题感觉是AC自动机+矩阵快速幂求解,但是由于AC自动机的 T i r e Tire Tire树结点最坏为400,矩阵快速幂为 O ( n 3 ) = 40 0 3 = 6.4 e 7 O(n^3)=400^3 =6.4e7 O(n3)=4003=6.4e7加上50组数据,超时了。但是由于 L L L很小,我们以 L L L为阶段 T i r e Tire Tire结点为状态DP即可。

T i r e Tire Tire结点数较小, L L L较大时,再使用矩阵快速幂。

		dp[0][0] = 1.0;
		ans = 0.0;
		for(int i = 0; i < L; ++i)
		{
			for(int j = 0; j <= AC.cnt; ++j)
			{
				if(AC.num[j]) continue;
				for(int k = 0; k < mp. size(); ++k)
				{
					int to = AC.ch[j][mp[k].first];
					if(AC.num[to]) continue;
					dp[i + 1][to] += mp[k].second * dp[i][j];
				}
			}
		}

【AC自动机解决矩阵匹配】:UVA - 11019 - Matrix Matcher

P P P阵分行插入 T i r e Tire Tire树,结尾标记是第几行,由于可能出现重复,应该用 v e c t o r vector vector维护。然后再分行查找 T T T阵,找到以后根据结尾标记推导出这个匹配串对应的 P P P阵的右上角坐标,开一个 C o u n t [ i ] [ j ] Count[i][j] Count[i][j]表示右上角为 ( i , j ) (i,j) (i,j) P P P阵存在的匹配的不同行的数量。最后扫描 C o u n t Count Count数组,如果为 P P P阵的行数证明以该点为右上角的 P P P阵已经完全匹配。


你可能感兴趣的:(ACM——字符串)