【模板】字符串哈希

字符串哈希:将字符串映射成为整数(p进制数,模q)并保证字符串不同,得到的哈希值不同,用来判断一个子串该串是否重复出现过

就是来求字符串是否相同或者包含的

 

经验:p取131、13331较好, q取2^64较好(unsigned long long)

hash(i+1) = hash * p + str[i+1]

hash(L-R) = hash(R) - hash(L-1) * p^(R-L+1) 这部分可以采用数组存下来,表示p[R-L+1]

 

回文字符串,补#号

奇数个 abcba 补成 a#b# c #b#a  可以发现仍然是奇数个

偶数个 abba   补成 a#b # b#a   补完之后仍然是奇数个

(这样就不需要对于回文串是奇数还是偶数来分情况讨论了)

 除此之外,字符串哈希常常与二分查找混合使用

 字符串哈希的基本代码:

#include <iostream>
using namespace std;

typedef unsigned long long ULL;
const int N = 100010;
const int base = 131;//13331
ULL h[N],p[N];
ULL get(int l,int r){
    return h[r] - h[l-1]*p[r-l+1];
}
int main(){
    string s;
    cin>>s;
    int len = s.length();
    p[0] = 1;
    for(int i = 1; i <= len ; i++){
        h[i] = h[i-1]*base + s[i-1] - 'a' +1;//存每一位的哈希值
        p[i] = p[i-1]*base;
    }
    //如果是判断一个区间里面的字符串
    int l1,l2,r1,r2;
    cin>>l1>>l2>>r1>>r2;
    if(get(l1,r1) == get(l2,r2))    cout<<"Yes"<<endl;
    else cout<<"No"<<endl;

}

 

进阶:回文子串的最大长度

题目来源:https://www.acwing.com/problem/content/141/

同样是利用字符串哈希,预先处理前缀哈希和后缀哈希,再利用二分的方法,枚举中点进行匹配求解

利用类似于#的分隔符处理回文中心

二分注意二分区间,以及匹配时注意后缀哈希的处理

#include <iostream>
#include <string.h>
#include <algorithm>
using namespace std;
typedef unsigned long long ULL;
const int N = 2e6+10;
const int base = 131;
ULL hl[N],hr[N],p[N];
ULL get(ULL h[] ,int l,int r){
    return h[r] - h[l-1] * p[r-l+1]; 
}
char s[N];
int main(){
    int T = 1;
    while(cin>>s+1 ,strcmp(s+1,"END")){//循环只看逗号后面的数值 
        int len = strlen(s+1);        
        for(int i = 2*len; i > 0;i -= 2){//倒序操作,可以节省空间 
            s[i] = s[i/2];
            s[i-1] = 'z'+1;//这里要注意不一定要用#,但是一定要不能在a-z的范围内 
        }
        len *= 2;//长度*2 
        p[0] = 1;
        for(int i = 1,j = len; i <= len; i++,j--){
            hl[i] = hl[i-1]*base + s[i] - 'a'+1;
            hr[i] = hr[i-1]*base + s[j] - 'a'+1;
            p[i] = p[i-1]*base;
        }
        int res = 0;
        for(int i = 1; i <= len; i++){
            int l = 0, r = min(i-1,len- i);
            while(l < r){
                int mid = l+r+1>>1;//补上+1,防止出现死循环 
                if(get(hl,i-mid,i-1) != get(hr,len-(i+mid)+1,len-(i+1)+1))    r = mid -1;
                else l = mid;
            }
            //此时长度为添加分隔符情况,需要具体求出真实长度 
            if(s[i - l] <= 'z')    res = max(res,l+1);//字母比分隔符多1,((2*l+1)+1)/2 = l+1 
            else res = max(res,l); //字母比分隔符少1,(2*l+1-1)/2 = l; 
            
        }
        cout<<"Case "<<T<<": "<<res<<endl;
        T++;
    }
} 

 

后缀数组

题目来源:https://www.acwing.com/problem/content/142/

这题也是采用了哈希字符串的思想,基本模板套路和上面的字符串匹配类似,也是采用了字符串哈希+二分的思想,降复杂度

我觉得 yxc大佬的视频讲的很好了,之后忘记了可以再重新看一遍

#include <iostream>
#include <algorithm>
#include <string.h>
#include <climits>
using namespace std;
typedef unsigned long long ULL;
const int base = 131;
const int N = 300010;
ULL h[N],p[N];
int sa[N],len;
char str[N];
ULL get(int l,int r){
    return h[r] - h[l-1]*p[r-l+1];
}
int get_common(int a,int b){//计算最长公共前缀的长度 
    int l = 0 ,r = min(len- a + 1,len - b + 1);
    while(l < r){
        int mid = (l + r + 1 )/2;
        if(get(a,a + mid -1) != get(b,b + mid -1))    r = mid - 1;
        else l = mid;
    }
    return l;
}

bool cmp(int a,int b){
    int l = get_common(a,b);
    int a_val = a + l > len ? INT_MIN : str[a+l];
    int b_val = b + l > len ? INT_MIN : str[b+l];
    return a_val < b_val;
}
int main()
{
        cin>>str+1;
        len = strlen(str+1);
        
        p[0] = 1;
        for(int i = 1; i <= len ; i++){
            h[i] = h[i-1]*base + str[i]-'a'+1;
            p[i] = p[i-1]*base;
            sa[i] = i;
        }
        //对sa数组进行特定排序 
        sort(sa+1,sa+1+len,cmp);
        
        for(int i = 1; i <= len;i++){
            cout<<sa[i]-1<<" ";
        }
        cout<<endl;
        for(int i = 1; i <= len ;i ++){
            if(i == 1)    cout<<0<<" ";
            else cout<<get_common(sa[i-1],sa[i])<<" ";
        }
        cout<<endl;
} 

 

posted @ 2020-10-04 17:16  我不秃  阅读(179)  评论(0编辑  收藏  举报