CEOI2011 mat solution中文翻译


这个问题描述了一个字符串匹配的变种问题。给定两个串,模式串p[1..n]和文本串t[1..m]。任务是找出所有的位置j,1 <= j <= m - n + 1,满足模式串在位置j匹配文本串。而且,在这个问题中,模式串和文本串都是互异整数列。
而且,模式串p不是直接给出来的。给定一个数列s来描述p:s1是p中最小的元素,s2是第二小的……任意一个等于输入的p都是等价的,所以我们假定p是1..n的一个排列。这种表示很容易由s在O(n)计算出来。
在大部分模式串匹配问题中,模式串与母串在位置j匹配当且仅当p=t[j..j+n-1]。这个问题给出了一种不同的串匹配定义,模式串与母串匹配,当且仅当p与t[j..j+n-1]是同构的。我们称呼两个长度为k的串同构,当且仅当a[i] < a[j] <==> b[i] < b[j] forall i <= i, j <= k。
为简化符号,我们把a与b同构写成a~b。下面,我们有两个简单但是重要的结论。给定三个长为k的序列a,b,c。
1. 如果a ~ b,则a[x..y] ~ b[x..y] for i <= x <= y <= k
2. 如果a ~ b且b ~ c,则a ~ c。

为了解决这个问题,我们拓展了KMP算法来满足需求。接下来,我们假定读者熟悉KMP算法。

失败指针:
我们定义:一个序列a[1..k]的边界为,a[1..k]的一个长为t的后缀,且这个后缀与a[1..t]相似。在KMP算法中,我们一开始要计算失败指针f。对于每个1 <= i <= n,我们想知道串p[1..i]除自己本身外的最长边界是什么:
f[i] = max {p[1..k] ~ p[i - k + 1..i]} 0 <= k < i
另外,我们把f[0]设为0。
我们以i递增的顺序来计算f[i]。p[1..i]的最长边界包括p[1..i - 1]的一部分和字母p[i]。我们遍历p[1..i - 1]的所有边界,从最长的开始,对于每个边界,我们检查添加一个字母p[i]后能否构成p[1..i]的一个边界。
我们用下面的引理来遍历所有边界,它的证明稍后给出。

引理1:p[1..i]的所有边界的长度依次为f[i], f[f[i]], f[f[f[i]]], f[f[f[f[i]]]]...
注意:由于0 <= f[i] < i,上述序列从某些点开始就只出现0了。
现在仍然遗留一个问题:如何判断p[1..i - 1]的一个边界加上一个字母p[i]后可以构成p[1..i]的一个边界。换句话说,给定两个串a[1..k]、b[1..k](前者是模式串的一个前缀,后者是模式串的一个子串),已知a[1..k - 1] ~ b[1..k - 1],如何判断a[1..k] ~ b[1..k]。注意到这就是判断是否:
a[q] < a[k] <--> b[q] < b[k] for all 1 <= q < k
这可以按照下面这个方式重新表述(注意每个序列a、b的元素都是不同的):
性质1:对于一些1 <= r <= k,a[k]是a[1..k]中第r大的元素,b[k]是b[1..k]中第r大的元素。
我们现在描述一个检查是否满足上述条件的方法。
设a[u]是a[1..k - 1]中比a[k]小的元素中最大的一个,a[w]是a[1..k - 1]中比a[k]大的元素中最小的一个。我们假定这些元素存在,其他情况类似。根据定义,a[u] < a[k] < a[w]。我们可以知道判断b[u] < b[k] < b[w]是否成立是与判断性质1等价的。这是因为a[1..k - 1] ~ b[1..k - 1],所以a[1..k - 1]中比a[u]小的数的个数等于b[1..k - 1]中比b[u]小的数的个数。类似的,a[1..k - 1]中比a[w]大的数的个数等于b[1..k - 1]中比b[w]大的
数的个数。所以,这个检测与性质1实质上是等价的。
现在,我们讨论如何计算u和w的下标。对于每个1 <= i <= n,我们需要在p[1..i]找到比p[i]小的最大元素,把这个下标记作g[i]。我们也需要知道比p[i]大的最小元素,记作h[i](这是一个对称的问题)。
注意到p[1..n]是一个1..n的排列。我们维护一个由p[1..i]的所有元素构成的递增的双向链表。初始时他只是一个1..n的所有元素构成的链表。每一步都要删除一个元素。我们记录链表中的每个元素在p[1..n]中的位置,也记录p[1..n]的每个元素在链表中的位置。链表允许我们对于每个i,可以在常数时间内获得与p[i]最接近的元素——他们只是链表中,删除了n - i个元素之后,p[i]的前驱与后继。
这就给出了一个计算失败指针的算法。O(n)预处理出数组g[i..n]与它的对称问题h[i..n]之后,算法就与KMP算法的失败指针的计算完全一致了。整个算法的运行时间为O(n)。

寻找匹配

KMP匹配算法的主要过程,大概讲如下:
给定模式串的一部分,尽量拓展一个字符。如果不可行,用失败指针得到一个稍短的部分匹配,继续匹配文字。

这也是我们在这个问题中要做的事。用上面的方法,我们能够在常数时间内判断一个部分匹配能不能再拓展一个字符。正确性的证明很简单,主要思想是:如果我们跳过一些正确的匹配(即,我们用失败指针把部分匹配移动得太多超过了匹配的开始),我们马上就得到这与失败指针的定义是矛盾的。
最后,由于匹配过程只是KMP算法的轻微修改,所以在O(n+m)的时间内可以出解。所以,整个算法只需要线性时间。


引理1的证明:
我们证明,如果p[1..i]有一个长度为t的边界,那么比t短的最长的边界长度为f[t]。如果f[t] = 0,那么长度为t的边界是最短的一个。从边界的定义可以知道,p[1..t] ~ p[i - t + 1..i]。
我们首先证明p[1..i]有一个长度为f[t]的边界。首先,注意到p[1..t] ~ p[i - t + 1..i]。由此得出,对于每个1 <= s < t,p[1..t]有一个长度为s的边界。此外,任意一个p[1..t]的边界与p[i - t + 1..i]的边界相似。所以,一个长度为f[t]的p[1..t]的边界与长为f[t]的p[i - t + 1..i]的边界相似。这就表明p[1..f[t]] ~ p[i - f[t] + 1..i]。
为了完成这个证明,我们得证明没有长度严格在f[t]与t之间的边界存在。为了证明这个,我们证明每一个这样长度的边界一定也是p[1..t]的边界,否则会和f[t]的定义相矛盾。设f[t] < u < t且p[1..u] ~ p[i - u + 1..i],也就是说,p[1..i]有一个长度为u

的边界。这意味着p[1..t]的一个长度为u的后缀与p[1..t + 1..t]的一个长度为u的后缀相似。我们已经知道p[1..t]和p[i - t + 1..i]相似,所欲p[i - t + 1..i]的一个长为u的后缀与p[1..i]的一个长为u的后缀相似。所以p[1..t]的一个长为u的后缀与p[1..u]相似,矛盾。

膜拜宇宙大农统neroysq!



你可能感兴趣的:(算法,任务,c)