🔖 acmAho-Corasick 自动机矩阵快速幂动态规划解题报告

问题简述

N 个由小写字母构成的单词。一个串的权值为这个串中每个单词出现的次数的总和(单词可部分重叠)。求一个长度为 L 串的最大权值。

数据范围:N 个单词长度总和不超过 100,1L1015.

样例输入:

1
2
3
4
3 15
agva
agvagva
gvagva

样例输出:

1
11

问题简析

先将 N 个串建成 Aho-Corasick 自动机,并记 S 为此自动机的状态转移图的节点集。用 dp(i,s) 表示长度为 i 且最后一个字符在 Aho-Corasick 自动机的节点 s 上的串的最大权值。显然,答案为 max{dp(L,s) | sS}.

状态转移方程:

dp(i+1,s)=max{dp(i,s) | sS且存在从 s 到 s 的边}+vs.

其中,vs 为以该节点结尾的单词的个数(这是经典的 AC 自动机基础操作,此处略去细节)。

样例的状态图如下

Aho-Corasick1.png

其中,虚线为 fail 指针。上图中,v4=1,v7=3,v13=2,其它节点 v 值为 0. 不难发现,仅考虑有实线的边的转移是最优的;当没有实线的边时,选择虚线的边转移。

接下来就是重头戏了。我们可以构造一个 |S|×|S|=14×14 [1] 的矩阵 M:

(1)M(s,s)={vs,存在一条边ssINF,不存在一条边ss

结合前面的状态转移方程有:dp(i+1,s)=max{dp(i,s)+M(s,s)|sS}.

如果将 dp(i) 当做一个列向量,那么 dp(i+1) 可以看做由 Mdp(i) 进行如转移方程所示的运算规则得到。对比传统的矩阵乘法,相当于: 变成了 max,同时 × 变成了 +. 不难验证,新的矩阵运算同样是左结合的。不妨记上述状态转移方程的运算符为 ,不妨记 N=|S|1,构造如下矩阵运算:

(2)(dpi+1,0dpi+1,0dpi+1,0dpi+1,1dpi+1,1dpi+1,1dpi+1,Ndpi+1,Ndpi+1,N)(3)(4)=(M0,0M0,1M0,NM1,0M1,1M1,NMN,0MN,1MN,N)(dpi,0dpi,0dpi,0dpi,1dpi,1dpi,1dpi,Ndpi,Ndpi,N)

不难发现:

(5)dpi+1,s=(Ms,0Ms,1Ms,N)(dpi,0dpi,1dpi,N)

这和转移方程是对应的,这意味着:dp(L)=Mdp(L1)=ML×dp(0). 接下来,矩阵“快速幂”就好了。

等等,dp(0) 是什么?因为 dp(0) 表示零个字符,只能在状态 0,其它状态必须设为 ,即:

(6)dp(0,i)={0,i=0INF,i0

可以通过 dp(1)=Mdp(0) 来验证。

程序实现

solution.cpp 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
typedef long long LL;
const LL LNF = 0x3f3f3f3f3f3f3f3fLL;
struct Matrix {
static const int MAXN = 100 + 10;
LL M[MAXN][MAXN];
void fill(LL val) {
for (int i = 0; i < MAXN; ++i)
for (int j = 0; j < MAXN; ++j) M[i][j] = val;
}
friend Matrix operator*(const Matrix& A, const Matrix& B) {
Matrix C;
C.fill(-LNF);
for (int i = 0; i < MAXN; ++i)
for (int j = 0; j < MAXN; ++j)
for (int k = 0; k < MAXN; ++k)
C.M[i][j] = max(C.M[i][j], A.M[i][k] + B.M[k][j]);
return C;
}
static Matrix Power(Matrix A, LL X) {
Matrix ans;
ans.fill(-LNF);
for (int i = 0; i < MAXN; ++i) ans.M[i][i] = 0;
for (; X > 0; X >>= 1, A = A * A)
if (X & 1) ans = ans * A;
return ans;
}
void show(int N = MAXN) const {
for (int i = 0; i < N; ++i) printf("%6d ", i);
printf("\n-------------------------------------------------------------\n");
for (int i = 0; i < N; ++i) {
printf("%2d:|", i);
for (int j = 0; j < N; ++j) printf("%6d|", M[i][j]);
printf("\n");
}
printf("-------------------------------------------------------------\n");
}
};
struct AhoCorasick {
static const int SIGMA_SIZ = 26;
static const int MAX_NODES = 100 + 10;
int ch[MAX_NODES][SIGMA_SIZ];
int val[MAX_NODES];
int fail[MAX_NODES];
int siz;
void init() {
siz = 1;
memset(ch[0], 0, sizeof ch[0]);
}
int idx(const char c) const {
return c - 'a';
}
void insert(const char* s) {
int r = 0;
for (; *s; ++s) {
int c = idx(*s);
if (!ch[r][c]) {
memset(ch[siz], 0, sizeof ch[siz]);
val[siz] = 0;
ch[r][c] = siz++;
}
r = ch[r][c];
}
++val[r];
}
void calcFail() {
static queue<int> Q;
for (int c = 0; c < SIGMA_SIZ; ++c) {
int o = ch[0][c];
if (o) fail[o] = 0, Q.push(o);
}
while (!Q.empty()) {
int r = Q.front();
Q.pop();
for (int c = 0; c < SIGMA_SIZ; ++c) {
int o = ch[r][c];
if (o) {
int fo = fail[r];
for (; fo && !ch[fo][c]; fo = fail[fo])
;
fail[o] = ch[fo][c];
val[o] += val[fail[o]];
Q.push(o);
} else {
ch[r][c] = ch[fail[r]][c];
}
}
}
}
void buildMatrix(Matrix& mat) {
mat.fill(-LNF);
for (int r = 0; r < siz; ++r)
for (int c = 0; c < SIGMA_SIZ; ++c) mat.M[ch[r][c]][r] = val[ch[r][c]];
}
};
Matrix mat;
AhoCorasick ac;
int N;
LL M;
char s[200];
int main() {
ac.init();
scanf("%d%lld", &N, &M);
for (int i = 0; i < N; ++i) {
scanf("%s", s);
ac.insert(s);
}
ac.calcFail();
ac.buildMatrix(mat);
// for(int i=0; i < ac.siz; ++i) printf("%d, fail[%d]=%d\n", i, i,
// ac.fail[i]); mat.show(ac.siz);
mat = Matrix::Power(mat, M);
LL ans = 0;
for (int i = 0; i < Matrix::MAXN; ++i) ans = max(ans, mat.M[i][0]);
printf("%lld\n", ans);
return 0;
}
© 2017-2026 光和尘有花满渚、有酒盈瓯

Comments