字符串算法总结

字符串 Hash

字符串 Hash\(\text{Hash}(a)=\sum{a_i x^i} \bmod{p}\)。本质是一种进制的思想。

双 Hash:保证正确性。

#define mod1 1000000007
#define mod2 1000000009

ll n,p1[251],p2[251],p1b[251],p2b[251],s1[251],s2[251];
char c[251];
ll qpow(ll x,ll y,ll mod) {
    ll res=1;
    while(y) {
        if(y&1) res=res*x%mod;
        x=x*x%mod; y>>=1;
    }
    return res;
}
int main() {
    scanf("%lld",&n); scanf("%s",c+1);
    int l1,r1,l2,r2; scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
    if((r1-l1)!=(r2-l2)) {printf("No"); return 0; }
    p1[0]=p2[0]=p1b[0]=p2b[0]=1;
    for (int i=1;i<=n;i++) {
        p1[i]=p1[i-1]*347%mod1;
        p2[i]=p2[i-1]*347%mod2;
        if(i==1) {
            p1b[i]=qpow(347,mod1-2,mod1);
            p2b[i]=qpow(347,mod2-2,mod2);
        } else {
            p1b[i]=p1b[i-1]*p1b[1]%mod1;
            p2b[i]=p2b[i-1]*p2b[1]%mod2;
        }
        s1[i]=(s1[i-1]+c[i]*p1[i])%mod1;
        s2[i]=(s2[i-1]+c[i]*p2[i])%mod2;
    }
    ll hash1,hash2,hash3,hash4;
    hash1=(s1[r1]-s1[l1-1]+mod1)%mod1*p1b[l1-1]%mod1;
    hash2=(s2[r1]-s2[l1-1]+mod2)%mod2*p2b[l1-1]%mod2;
    hash3=(s1[r2]-s1[l2-1]+mod1)%mod1*p1b[l2-1]%mod1;
    hash4=(s2[r2]-s2[l2-1]+mod2)%mod2*p2b[l2-1]%mod2;
    if(hash1==hash3&&hash2==hash4) printf("Yes %lld %lld %lld %lld",hash1,hash3,hash2,hash4)
    else printf("No %lld %lld %lld %lld",hash1,hash3,hash2,hash4);
    return 0;
}


Knuth–Morris–Pratt (KMP)

字符串匹配问题:给出文本串模式串,求出模式串在文本串中所有出现的位置。

利用已经部分匹配这个有效信息,保持i指针不回溯,通过修改j指针,让模式串尽量地移动到有效的位置。

\(P_k=P_j\) 时,有 \(\text{next}(j+1)=\text{next}(j) + 1\)

\(P_k\ne P_j\) 时,有 \(k=\text{next}(k)\)

推荐:学习 KMP 看 Link link,复习 KMP 看 Link link

1074180-20181030081654830-1783877527.png

char s[N], p[M];
int sl, pl;

int nex[M];

inline void getnex() { // 求前缀数组
    int k=-1, j=0; nex[0]=-1;
    while (j<pl) {
        if (k==-1 || p[j]==p[k]) { // 当两个字符相等时要跳过
            if (p[++j]==p[++k]) nex[j]=nex[k]; else nex[j]=k;
        } else k=nex[k];
    }
}

inline void kmp() {
    int i=0, j=0; // i主串的位置,j模式串的位置
    while (i<sl && j<pl) {
        if (j==-1 || s[i]==p[j]) i++, j++; else j=nex[j]; // 当j为-1时,要移动的是i,当然j也要归0
        if (j==pl) printf("%d\n", i-pl+1), j=nex[j];
    }
}

int main() {
    scanf("%s", s); scanf("%s", p);
    sl=strlen(s); pl=strlen(p);
    getnex();
    kmp();
    return 0;
}


字典树 Trie

按前缀分类。

具体形态为一棵有根树,每条边上记录一个字符,从根到一个节点路径上所有字符接起来即这个节点所代表的字符串。实现时,我们对每个节点维护与字符集一一对应的儿子集合。插入字符串时,依次遍历串中每个字符,并从根开始走每个字符对应的儿子,如果不存在则新建对应节点。时间复杂度为 字符串总长,空间复杂度为 字符串总长 \(\times\) 字符集。

空间优化:用 map 来存储儿子优化空间,代价是时间复杂度多一个 log。


转载于:https://www.cnblogs.com/greyqz/p/string.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值