小科技之卷积解决字符串匹配问题
小科技之卷积解决字符串匹配问题
OI中有各种字符串匹配问题,常见的有单模式串匹配、多模式串匹配和子串匹配等,一般可以用KMP、AC自动机、SAM解决。如果涉及到其他形式的单模式串匹配,这些传统的方式很不好解决,有没有更灵活的处理方法或者通法呢?
答案是有的,而且就是耳熟能详的FFT/NTT。
我们先考虑最基础的单模式串匹配,求长为m的字符串T在长为n的字符串S里出现了多少次,只不过我们要用卷积来实现。具体的,我们设\(C(i,j)\)表示\((s_i-t_j)\),即这两位是否相等,相等就为0,如果匹配了,那么自然有\(\sum\limits_{i=0}^{m-1}C(x-m+i+1,i)=0\),但是这样有反例,例如\(ab\)和\(ba\)都可以和\(ab\)匹配,因此我们改成\(\sum\limits_{i=0}^{m-1}\left|C(x-m+i+1,i)\right|=0\)才行。但是我们维护这个也是\(O(m)\)的,所以我们考虑给这一项平方,再设\(ans(x)=\sum\limits_{i=0}^{m-1}C(x-m+i+1,i)^2=\sum\limits_{i=0}^{m-1}(s_{x-m+i+1}-t_{i})^2\),我们暴力拆柿子,得:
这时前两项可以预处理后\(O(1)\)算,问题就在于怎么求最后一项。我们发现这个形式不好看,于是我们把T翻转,即\(t_i\)变成\(t_{m-i-1}\),那么就变成了
注意到\(x-m+i+1+m-i-1=x\),那么这显然就是一个卷积式,我们可以\(O(n\log n)\)求出每一项,这样就可以\(O(n\log n)\)求出\(ans(x)\)了。
从这个算法得到启发,我们来总结做题的方法。
第一步:设计通配函数,即上文中的\(C(i,j)\)。
第二步:设计完全通配函数,即上文中的\(ans(x)\)。
第三步:计算每一位的完全通配函数的值。
来看几道简单的例题叭
题意:带通配符\(*\)的单模式串匹配。
思路:因为有通配符的存在,所以我们设计\(C(i,j)=s_it_j(s_i-t_j)^2\),为什么要这样设计呢?因为对应位置相等的情况要么就是\(s_i=t_j\ne *\),要么就是\(s_i=*||t_j=*\),因此我们把通配符当成0,这样设计匹配函数就可以满足上面的条件了。
显然,完全通配函数就是
如何快速计算呢?我们把b翻转,就可以得到
这个形式就和卷积一样了,我们只需把\(s^3\)和\(t\)卷起来,再把\(s^2\)和\(t^2\)卷起来,最后把\(s\)和\(t^3\)卷起来就可以了。复杂度\(O(n\log n)\)。
题意:定义两个字符串匹配当且仅当T中每一个字符可以在S中找到一个相距不超过k的相同字符,求T在S中出现次数,字符集大小为4。
思路:这一题乍一看不好直接设计通配函数,我们发现这一题的字符集大小只有4,于是尝试对每一位分开计算。具体的当处理到字符ch时,我们把和ch相同的当成1,其他的是0,然后对S串正反扫一遍把所有与1相距不超过k的位置也标成1,这时两位匹配当且仅当\(t_i=0\)或\(t_i=s_i=1\),这样我们设计匹配函数\(C(i,j)=t_j(a_i-t_j)^2\)就满足条件了。
显然,完全匹配函数就是
根据我们的定义,\(f_i,g_i\)为0或1,那\(f^k_i=f_i,g^k_i=g_i\),于是就把原式化简为
同样的,把T翻转,那么
这样就可以卷积了。复杂度\(O(n\log n)\)
题意:求有多少个S的子串满足修改小于等于3个位置能够变成T,字符集大小为4。
思路:看到字符集大小很小,就想到对每一种字符单独考虑。S、T中和字符c相等的位置为1,定义匹配函数为\(C(i,j)=s_i\times t_j\),那自然\(C(i,j)=1\)时\(s_i\)与\(t_j\)匹配。
这时完全匹配函数就呼之欲出了。
如果\(ans(x)+3\leqslant m\)就合法
显然,计算时将T翻转 (是不是很套路?) ,得:
这样就可以愉快地卷积了。复杂度\(O(n\log n)\)。