【求解有几个模式串在主串中出现过】: 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阵已经完全匹配。