动态规划和字符串匹配(KMP、AC自动机)
2013-04-17 19:12
351 查看
最近学了关于字符串匹配主要的两种方法,做了一些题目,发现这可以动态规划结合,题目中往往有“……T是S的子串……”这类字眼,而且答案要求“最少、最多、多少种”一类的问题。
解决这类问题往往是要记录一个“匹配到哪一位”的状态,然后考虑当前状态可以更新哪些新的状态,下面列举两道题目:
很容易想到一个这样的动态规划:以f(i,j)记录S字符串第i位,匹配了T的前j位,最少需要删除多少位。然后对于每一位,有删除和不删除两种情况。下面是一个例子:
我们用当前的状态去更新新的状态,现在需要考虑第i+1位的A是否删除,如果删除,匹配的个数j不变,那么:f(i+1,j)= f(i,j)+ 1;如果不删除,匹配的个数是多少呢?
当然,不能够一个个枚举。这时可以发现,求新的匹配的个数正好是KMP中匹配失败后的所需要求的。
那么,这样时间复杂度貌似就是预处理的O(|T|)加上状态O(|S| × |T|)。但后来发现求新的匹配个数时超时,因为很多求next的过程是一样的。如:
首先,i = 4, j = 4,也就是T串,现在匹配了4个,现在考虑不删除i+1位,则要求出匹配的个数,得到了T’,然而T’就是f(i,next[j]),发现还是不行,则又有T’’ : f(i,next[ next[j ] ]),还是不行,那么就是一个也匹配不了。那么在求完了f(i,j)后,结果也是T’、T’’的结果,所以可以同时计算这几个的结果。这样就不超了。
样例输入:
3 7
ABA
CB
ABACB
样例输出:
4
样例解释:
构造出 ABACBCB,则有1个ABA,2个CB,1个ABACB。
设计一个这样的动态规划:f(i, j)记录下,已经了构造第i个字符,匹配的状态为字母树的标号为j的结点(这个可以用静态数组来保存字母树,然后数组第j个结点就是标号为j的结点),最多可以出现多少个字符串,然后可以枚举第 i+1 构造的字母,去更新f(i+1, ?)。这个可以在字母树上沿着失败指针走一次即可。
解决这类问题往往是要记录一个“匹配到哪一位”的状态,然后考虑当前状态可以更新哪些新的状态,下面列举两道题目:
KMP
有两个字符串S和T(1 ≤ |S| ≤10000, 1 ≤ |T| ≤ 1000),问题是至少要从字符串S中删掉多少个字符,才能使得字符串T不是字符串S的子串。很容易想到一个这样的动态规划:以f(i,j)记录S字符串第i位,匹配了T的前j位,最少需要删除多少位。然后对于每一位,有删除和不删除两种情况。下面是一个例子:
i | i + 1 | ||||
S | …… | A | B | A | …… |
T | A | B | C | ||
j |
i | i + 1 | |||||
S | …… | A | B | A | …… | …… |
T | A | B | C | |||
T’ | A | B | C | |||
j |
那么,这样时间复杂度貌似就是预处理的O(|T|)加上状态O(|S| × |T|)。但后来发现求新的匹配个数时超时,因为很多求next的过程是一样的。如:
1 | 2 | 3 | i = 4 | 5 | 6 | |||
S | A | B | A | B | C | A | ||
T | A | B | A | B | A | |||
T’ | A | B | A | B | A | |||
T’’ | A | B | A | …… |
AC自动机
给出n(1 ≤ n ≤ 20, 长度小于 20)个字符串,要求构造长度为K的字符串,使得这n个字符串出现的次数最多。样例输入:
3 7
ABA
CB
ABACB
样例输出:
4
样例解释:
构造出 ABACBCB,则有1个ABA,2个CB,1个ABACB。
设计一个这样的动态规划:f(i, j)记录下,已经了构造第i个字符,匹配的状态为字母树的标号为j的结点(这个可以用静态数组来保存字母树,然后数组第j个结点就是标号为j的结点),最多可以出现多少个字符串,然后可以枚举第 i+1 构造的字母,去更新f(i+1, ?)。这个可以在字母树上沿着失败指针走一次即可。
#include <cstdio> #include <cstring> using namespace std; const int inf = 0x3f3f3f3f; char S[10007], T[1007]; int next[1007]; int f[10007][1007]; int main() { freopen("necklace.in", "r", stdin); freopen("necklace.out", "w", stdout); scanf("%s\n%s", S, T); int n = strlen(S); int m = strlen(T); int j = -1; next[0] = -1; for (int i = 1; i < m; i ++) { while (j != -1 && T[j + 1] != T[i]) j = next[j]; if (T[j + 1] == T[i]) j ++; next[i] = j; } for (int i = 0; i < n; i ++) for (int j = 0; j < m; j ++) f[i][j] = inf; //全部为无穷大 if (S[0] == T[0]) { //f[i] 初始化 f[0][1] = 0; f[0][0] = 1; } else f[0][0] = 0; for (int i = 0; i < n - 1; i ++) { for (int j = -1; j < m - 1; j ++) //-1表示一个也匹配不了,0为匹配一个,以此类推 使用时下表偏移+1 f[i + 1][j + 1] <?= f[i][j + 1] + 1; //删除的情况 for (int j = m - 2; j >= -1; j --) if (f[i][j + 1] != inf) { //有可能达到该状态 int tmp = j, tmp2 = inf; //tmp为当前状态, tmp2为最小可以用来更新的值 while (tmp != -1 && T[tmp + 1] != S[i + 1]) { tmp2 <?= f[i][tmp + 1]; f[i][tmp + 1] = inf; //该状态同f(i, j),所以下次不用计算了。 tmp = next[tmp]; } tmp2 <?= f[i][tmp + 1]; f[i][tmp + 1] = inf; if (T[tmp + 1] == S[i + 1]) tmp ++; f[i + 1][tmp + 1] <?= tmp2; } } int ans = inf; for (int j = 0; j < m; j ++) //结果不能匹配m个, ans <?= f[n - 1][j]; printf("%d\n", ans); return 0; }
#include <cstdio> #include <cstring> using namespace std; struct Trie { Trie *son[3], *next; int cnt; Trie() { for (int i = 0; i < 3; i ++) son[i] = NULL; cnt = 0; } }mem[307], *root = mem, *que[307]; //静态数组, 下标为0的是根 int tot, f[1007][307]; char dat[17]; Trie* New() { //创建新结点 tot ++; return mem + tot; } int main() { freopen("combos.in", "r", stdin); freopen("combos.out", "w", stdout); int n, k; scanf("%d%d\n", &n, &k); for (int i = 0; i < n; i ++) { //建立字母树 scanf("%s\n", dat); int len = strlen(dat); Trie *now = root; for (int j = 0; j < len; j ++) { int p = dat[j] - 'A'; if (! now -> son[p]) now -> son[p] = New(); now = now -> son[p]; } now -> cnt ++; } int head = 0, tail = 0; que[0] = root; root -> next = NULL; //建立AC自动机 while (head <= tail) { Trie *now = que[head]; head ++; for (int i = 0; i < 3; i ++) if (now -> son[i]) { tail ++; que[tail] = now -> son[i]; now -> son[i] -> next = root; if (now != root) { Trie *tmp = now -> next; while (tmp) { if (tmp -> son[i]) { now -> son[i] -> next = tmp -> son[i]; break; } tmp = tmp -> next; } } } } memset(f, -1, sizeof(f)); //初始化 f[0][0] = 0; for (int i = 0; i < k; i ++) //构造第i位 for (int j = 0; j <= tot; j ++) //点的编号 if (f[i][j] > -1) { //该状态可以实现 for (int p = 0; p < 3; p ++) { //枚举第i+1个字母 Trie *now = mem + j; //编号为j的点 while (! now -> son[p] && now -> next) now = now -> next; if (! now -> son[p]) { f[i+1][0] >?= f[i][j]; //如果没找到,则更新根结点 continue; } now = now -> son[p]; Trie *tmp = now; int cntf = 0; //计数:可以新增多少个 while (tmp) { cntf += tmp -> cnt; tmp = tmp -> next; } f[i+1][now-mem] >?= f[i][j] + cntf; } } int ans = 0; for (int i = 0; i <= tot; i ++) ans >?= f[k][i]; printf("%d\n", ans); return 0; }
相关文章推荐
- BZOJ 1212 HNOI2004 L语言 AC自动机(Trie树)+动态规划
- 【AC自动机】基于自动机状态设计的动态规划
- 动态规划及空间压缩 ,串的模式匹配(KMP) Implement strStr()(leetcode)
- 动态规划问题
- 动态规划水题
- 双层动态规划_吃土豆问题
- 很特别的一个动态规划入门教程
- acm动态规划之LCS最长公共子串uva10405Longest Common Subsequence解题报告
- 动态规划解决0-1背包问题
- 动态规划
- 动态规划 triangle
- 动态规划之01背包问题
- 动态规划专题讲义之最大连续子序列之和
- 动态规划之最长公共子序列(LCS)
- 动态规划求解最长公共子串与公共子序列
- 动态规划的适用条件
- 增强学习(三)----- MDP的动态规划解法
- HDU 5418(动态规划-状压dp+floyd算法)
- 100道动态规划——34 UVA 10559 Blocks 状态的定义 状态转移方程
- 对人生进行动态规划