Luogu P8085 [COCI 2011/2012 #4] KRIPTOGRAM 题解 [ 蓝 ] [ KMP ] [ 哈希 ]
[COCI 2011/2012 #4] KRIPTOGRAM:KMP 处理子串匹配问题的经典题。
对于类似子串匹配的问题,适用 KMP 需要满足如下条件:
- 若 \(S = T\),则对于 \(S,T\) 任意两个长度相等的前缀,都必须是相等的。
- 子串相等必须满足传递性。即若 \(A = B, B =C\),则也一定有 \(A=C\)。
例如带通配符的子串匹配无法使用 KMP,是因为不具有传递性。
具体怎么证明我不知道,但是这个是杜老师说的,所以肯定是对的!!1
于是可以考虑对于此题如何判断两个字符是否相等,可以发现用每个位置的相同前驱的位置来判断就行了,具体而言:
- 若 \(a,b\) 的相同前驱都在待匹配的串内,则必须满足前驱的位置相等。
- 若 \(a\) 的相同前驱不在串内,则 \(b\) 的相同前驱必须不存在。
其中 \(a\) 表示待匹配的串,\(b\) 表示模式串。
跑 KMP 即可,用哈希表处理前驱即可做到时间复杂度 \(O(n)\)。
另一种做法是哈希,判断方式也是根据每个位置的相同前驱的位置来设计哈希函数,大致一样此处不再赘述。
#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi=pair<int,int>;
const int N = 2000005;
int idx = 0, a[N], b[N], n, m, pre[N], x[N], y[N], ne[N];
map<string, int> ys;
string s;
bool equa(int *a, int i, int *b, int j)
{
return ((i - a[i] == j - b[j]) || (b[j] == 0 && a[i] <= max(0, i - j - 1)));
}
void KMP()
{
for(int i = 2, j = 0; i <= m; i++)
{
while(j && !equa(y, i, y, j + 1)) j = ne[j];
if(equa(y, i, y, j + 1)) j++;
ne[i] = j;
}
for(int i = 2, j = 0; i <= n; i++)
{
while(j && !equa(x, i, y, j + 1)) j = ne[j];
if(equa(x, i, y, j + 1)) j++;
if(j == m)
{
cout << i - m + 1;
return;
}
}
}
int main()
{
//freopen("sample.in","r",stdin);
//freopen("sample.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
while(cin >> s)
{
if(s == "$") break;
if(ys[s] == 0) ys[s] = ++idx;
a[++n] = ys[s];
}
while(cin >> s)
{
if(s == "$") break;
if(ys[s] == 0) ys[s] = ++idx;
b[++m] = ys[s];
}
for(int i = 1; i <= n; i++)
{
x[i] = pre[a[i]];
pre[a[i]] = i;
}
memset(pre, 0, sizeof(pre));
for(int i = 1; i <= m; i++)
{
y[i] = pre[b[i]];
pre[b[i]] = i;
}
KMP();
return 0;
}

浙公网安备 33010602011771号