ACM算法总结 字符串(三)




带通配符的单模式匹配

平常的单模式匹配就是给出一个模式串 t,给出一个文本串 s,问 s 中能够在哪些位点完全匹配 t,这个用 KMP 可以很好地解决。但是如果在模式串和文本串中有一些通配符,也就是这些符号匹配什么都可以,那这样就解决不了了。比如说 t=a*bs=aebr*ob,其中 * 表示通配符,那么 t 在 s 中可以匹配的位点为 1 5(下标从 1 开始)。

解决办法本质上是把匹配转变为一个数学公式 G(x),表示模式串在原串的 x 处的匹配公式,并且这个 G(x) 满足 ∀ x ≥ 0 , G ( x ) ≥ 0 \forall x\ge 0,G(x)\ge 0 x0,G(x)0,且当 G(x)=0 时表示可以匹配。一般 G(x) 由一些子公式组成。

对于带通配符的单模式匹配问题,我们可以设子函数 p(a, b) 表示 t[a] 和 s[b] 的匹配函数,p(a, b) 和 G 具有一样的性质,所以我们的主要目标就是如何构造函数 p,使得它满足这个性质。比如对于这个问题,我们可以把 * 设为0,然后设 p ( a , b ) = ( t [ a ] − s [ b ] ) 2 t [ a ] s [ b ] p(a,b)=(t[a]-s[b])^2t[a]s[b] p(a,b)=(t[a]s[b])2t[a]s[b],那么满足 p ( a , b ) ≥ 0 p(a,b)\ge0 p(a,b)0,并且仅当 t[a]=s[b] 或两个中有通配符时才成立 p(a, b)=0 ,其余情况都大于 0 。所以有 G ( x ) = ∑ i = 0 m − 1 p ( i , x + i ) G(x)=\sum\limits_{i=0}^{m-1}p(i,x+i) G(x)=i=0m1p(i,x+i) ,可以证明这个 G(x) 满足上面的限制。

进一步,我们把模式串 t 翻转(这是为了凑出常数),然后公式就变为 G ( x ) = ∑ i = 0 m − 1 p ( m − i − 1 , x + i ) G(x)=\sum\limits_{i=0}^{m-1}p(m-i-1,x+i) G(x)=i=0m1p(mi1,x+i) ,发现 m − i − 1 + x + i = m − 1 + x m-i-1+x+i=m-1+x mi1+x+i=m1+x,对于特定的 x 是一个常数,那把 p 带入展开之后,这不就是一个很长的卷积和式么。用几次 FFT 加速,就可以算出所有的 G(x),然后一一判断是否等于 0 来决定是否匹配。

其实一般的单模式匹配也可以这么做,只要改一下 p 函数,改成 p ( a , b ) = ( t [ a ] − s [ b ] ) 2 p(a,b)=(t[a]-s[b])^2 p(a,b)=(t[a]s[b])2 就可以了。



字符串哈希

双哈希一般可以很好地避免冲突,而单哈希则可以更好地处理字符串。

双哈希就是给定两个哈希值,分别计算两次单哈希,然后用一个二元组唯一表示一个字符串。

单哈希的一般方法如下:给定一个质数 P,一个模数 M,然后对于字符串 s,其 hash 数组: h a s h [ i ] = ( h a s h [ i − 1 ] ∗ P + s [ i ] ) % M hash[i]=(hash[i-1]*P+s[i])\%M hash[i]=(hash[i1]P+s[i])%M ,这里一般把 M 设为 unsigned long long 的上限,这样就可以自然溢出,然后 P 就选择一个中等大小的质数就行了。

算出 hash 数组之后,如果要求 s 的某个子串的 hash 值,则有 h a s h s [ l , . . . , r ] = h a s h [ r ] − h a s h [ l − 1 ] ∗ P r − l + 1 hash_{s[l,...,r]}=hash[r]-hash[l-1]*P^{r-l+1} hashs[l,...,r]=hash[r]hash[l1]Prl+1

const unsigned long long P=100007;
const int maxn=1e6+5;
char s[maxn],t[maxn];
unsigned long long h[maxn],p[maxn];

void build(char *s)
{
    //p[0]=1;
    //REP(i,1,maxn-5) p[i]=p[i-1]*P;
    for(int i=1;s[i];i++) h[i]=h[i-1]*P+s[i];
}

unsigned long long get_hash(int l,int r)
{
    return h[r]-h[l-1]*p[r-l+1];
}

有了字符串哈希之后,单模式匹配实际上就可以看成枚举文本串起始位置,然后用哈希值判断是否相同就行了(多模式匹配如果文本串×模式串数目可以接受的话,也可以用哈希)。

另外,按照这种哈希方式,一个字符串其实可以表示为 s 1 ∗ p n + s 2 ∗ p n − 1 + ⋯ + s n ∗ p s_1*p^{n}+s_2*p^{n-1}+\dots +s_n*p s1pn+s2pn1++snp ,其中 p 的指数其实是多少不重要,只要满足相邻相差 1 的关系就行了。然后我们就可以用线段树维护每一位的值,那么某个子串的哈希值实际上就是区间和除以一个系数。

一道例题 Subpalindromes :给出一个字符串和两种操作:(1)修改某一个位置的字符;(2)查询某个区间是否是回文串。

做法就是,首先如果一个字符串是回文串,那么它正反两个方向的哈希值是相等的。所以用两棵线段树维护哈希值,然后查询的时候把指数少的那个乘上相应的 p 的幂,然后判断是否相等即可。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值