对于匹配字符串m和目标字符串n,最朴素的思想就是对于每个起始位置都o(m)地直接比较字符是否相同,最后时间复杂度为O(m*n)这样的时间复杂度在大多数时候都很不乐观,因此需要一些技巧降低时间复杂度。

 

哈希算法:

我们可以将一段字符串映射为数值然后比较数值大小,若相等则匹配成功。这是理想情况下的,要达到这样的效果需要哈希算法具备一定的抗碰撞性(即不同的字符串不会映射到同一数值)。在这个前提下,要是计算每个长度为m的字符串的哈希值,最终复杂度必然还是O(m*n),这里可以用滚动哈希的方法优化。

对于匹配字符串 s=C₁C₂C₃.....C⒨    哈希值H=C₁*B^(m-1)+C₂*B^(m-2)+C₃*B^(m-3)+...+C⒨   其中B取一个非常大素数如(1e9+7),B^(m-1)表示B的m-1次方

对于目标字符串ss=A₁A₂A₃....A⒩    已知A₁开始长度为m的字符串的哈希值为h , 要得到A₂开始长度为m的字符串的哈希值只需 

h′ = h*B-A₁*B^m+D            其中D为第(m+1)位字符,  就可以在O(n)的时间复杂度内实现字符串匹配

 

例题:POJ  3690

先上代码:

 1 #include<iostream>
 2 #include<algorithm>
 3 #include<string>
 4 #include<set>
 5 #include<vector>
 6 #include<queue>
 7 #include<stack>
 8 #include<map>
 9 #include<cmath>
10 #include<string>
11 using namespace std;
12 typedef unsigned long long ull;
13 char s[1010][1010];
14 char pat[110][55][1010];
15 ull hash_[1010][1010], temp[1010][1010];
16 const ull B = 1e9 + 7, A = 9973;
17 int N, M, P, Q, T;
18 void comput_hash(char a[][1010], int n, int m)
19 {
20     int i, j, k;
21     ull e = 0, t1 = 1, t2 = 1;
22     for (i = 1; i <= Q; i++)
23         t1 *= A;
24     for (i = 1; i <= n; i++)
25     {
26         e = 0;
27         for (j = 1; j <= Q; j++)
28             e = e * A + a[i][j];
29         for (j = 1; j + Q - 1 <= m; j++)
30         {
31             temp[i][j] = e;
32             if (j + Q <= m)
33                 e = e * A - a[i][j] * t1 + a[i][j + Q];
34         }
35     }
36 
37     e = 0;
38     for (i = 1; i <= P; i++)
39         t2 *= B;
40     for (j = 1; j + Q - 1 <= m; j++)
41     {
42         e = 0;
43         for (i = 1; i <= P; i++)
44             e = e * B + temp[i][j];
45         for (i = 1; i + P - 1 <= n; i++)
46         {
47             hash_[i][j] = e;
48             if (i + P <= n)
49                 e = e * B - temp[i][j] * t2 + temp[i + P][j];
50         }
51     }
52 }
53 int main()
54 {
55     int i, j, k;
56     int cnt = 0, ans;
57     while (~scanf("%d%d%d%d%d", &N, &M, &T, &P, &Q))
58     {
59         if (N == 0 && M == 0 && P == 0 && Q == 0 && T == 0)
60             break;
61         for (i = 1; i <= N; i++)
62             scanf("%s", s[i] + 1);
63         for (k = 1; k <= T; k++)
64             for (i = 1; i <= P; i++)
65                 scanf("%s", pat[k][i] + 1);
66 
67         multiset<ull>ml;
68         for (i = 1; i <= T; i++)
69         {
70             comput_hash(pat[i], P, Q);
71             ml.insert(hash_[1][1]);
72         }
73 
74         comput_hash(s, N, M);
75         for (i = 1; i + P - 1 <= N; i++)
76             for (j = 1; j + Q - 1 <= M; j++)
77                 ml.erase(hash_[i][j]);
78 
79         ans = T - ml.size();
80         printf("Case %d: %d\n", ++cnt, ans);
81     }
82     return 0;
83 }

对于这道题,我们可以先只看一个维度,对于一个维度的匹配就是简单的板子题,就像之前所说进行匹配即可,在拓展到二维时,应该考虑行列均相同。

按一个维度的思想,我们要保证一个字符相同,然后是一段字符相同。

这里我们可以将每一行的一段字符串的哈希值视为“一个字符”’(哈希算法需要足够抗碰撞允许我们这么做)

然后我们继续在列方向上进行匹配即可。

在代码中 用unsigned long long 来存哈希值能让数据自然溢出,若不用ull,则应在数据溢出前模上一个足够大的素数,

以B=1e9+7 作为行的基数  A=9973作为列的基数

先计算每个匹配模式的hash值,放入multiset里,hash[ i ][ j ]表示以从 i 到 i+P  ,j 到 j+Q的匹配模式的哈希值

再计算匹配对象的,然后将匹配对象中出现过的hash值从multiset里剔除,

此时在multiset里剩下的就是未在匹配对象出现过的匹配模式的个数

那么答案就是用匹配模式的个数 T 减去multiset里元素个数

 

参考书籍————《挑战程序设计竞赛》

posted on 2020-11-27 21:54  丶蛋花拉面  阅读(379)  评论(0)    收藏  举报