【 哈希和哈希表】子串查找

【 哈希和哈希表】子串查找

时间限制: 1 Sec  内存限制: 128 MB
提交: 78  解决: 25
[提交] [状态] [讨论版] [命题人:admin]

题目描述

这是一道模板题。
给定一个字符串A和一个字符串B,求B在A中的出现次数。A和B中的字符均为英语大写字母或小写字母。
A中不同位置出现的B可重叠。

输入

输入共两行,分别是字符串A和字符串B。

输出

输出一个整数,表示B在A中的出现次数。

样例输入

zyzyzyz
zyz

样例输出

3

提示

1≤A,B的长度≤106,A、B仅包含大小写字母。

一开始用kmp做的,后来知道了hash,突然发现了一个新世界

就是将一个字符串映射到一个整数,将整数代替字符串,

显然会出现几个不同的字符串映射到同一个整数,这种情况是要避免的,不然你就wa了

显然这些数越分散,

或者说,嗯,把熵这个概念引进来比较好理解,

熵越大(越杂乱),

这样就越不会出现几个不同的字符串映射到同一个整数上的情况

用的双hash,一个hash,wa了

一般k,p不要用1e9+7 或者1e9+9,出题人可能会卡,这个题用了双hash,

所以1e9+7,1e9+9没问题,但是别的题不一定不会卡,嗯,还是避免的好

一般的hash

hash[i]=(hash[i-1]*k+id(a[i]) )mod(p)

子串求hash公式

hash=((hash[r]-hash[l-1]*k^{r-l+1})mod(p)+p)mod(p)

双hash就是hash两遍,

用<hash1,hash2>,表示一个字符串,注意这个有顺序的

即,<hash1,hash2> 不等于 <hash2,hash1>

放一个双hash的板子,嗯,题目是洛谷P3370

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pll;
map<pll,bool> M;
const int maxn = 1e6+7;
const pll k{1e9+9,1e9+7},p{1e9+7,1e9+9},one{1ll,1ll},zero{0ll,0ll};
pll operator - (pll a,pll b){return make_pair((a.first - b.first + p.first)%p.first,(a.second - b.second + p.second)%p.second);}
pll operator * (pll a,pll b){return make_pair((a.first * b.first)%p.first,(a.second * b.second)%p.second);}
pll operator + (pll a,pll b){return make_pair((a.first + b.first)%p.first,(a.second + b.second)%p.second);}
pll operator + (pll a,int b){return make_pair((a.first + b)%p.first,(a.second + b)%p.second);}
pll Hash(char a[],int len){
    pll ans = zero;
    for(int i=0;i<len;i++)
        ans = ans*k + a[i];
    return ans;
}
char a[maxn];
int main(){
    int n,cnt = 0;
    cin>>n;
    while(n--){
        cin>>a;
        pll x = Hash(a,strlen(a));
        if(!M.count(x))
            cnt++,M[x] = 1;
    }
    cout<<cnt<<endl;
    return 0;
}
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pll;
const int maxn = 1e6+7;
const ll k = 998244353, p = 1e9+7;
const ll s = 1e9+7, q = 1e9+9;
char a[maxn],b[maxn];
pll Hash(char a[],int len){
    pll ans{0ll,0ll};
    for(int i=0;i<len;i++){
        ans.first = (ans.first*k + a[i])%p;
        ans.second = (ans.second*s + a[i])%q;
    }
    return ans;
}
int main() {
    cin>>a>>b;
    int lb = strlen(b);
    pll x = Hash(b,lb);
    pll r = Hash(a,lb);
    pll l = {0ll,0ll};
    pll c = {1ll,1ll};
    for(int i=0;i<lb;i++){
        c.first = c.first*k%p;
        c.second = c.second*s%q;
    }
    ll cnt = 0;
    for(int i=lb;a[i];i++){
        pll ans = {
            (r.first - l.first*c.first%p + p)%p,
            (r.second - l.second*c.second%q + q)%q
        };
        if(ans == x){
            cnt++;
        }
        r = {
            (r.first*k%p + a[i])%p,
            (r.second*s%q + a[i])%q
        };
        l = {
            (l.first*k%p + a[i-lb])%p,
            (l.second*s%q + a[i-lb])%q
        };
    }
    pll ans = {
            (r.first - l.first*c.first%p + p)%p,
            (r.second - l.second*c.second%q + q)%q
    };
    if(ans == x)cnt++;

    cout<<cnt<<endl;

    return 0;
}

精简精简,搜了下c++重载操作,嗯,缩减代码的效果很强,就是写的时候

看着这一串重载头有点晕

下面有带注释的代码

纯代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pll;
const int maxn = 1e6+7;
const ll k = 1e9+9, p = 1e9+7;
const ll s = 1e9+7, q = 1e9+9;
pll Tc{k,s};
pll operator - (pll a,pll b){return make_pair((a.first - b.first + p)%p,(a.second - b.second + q)%q);}
pll operator * (pll a,pll b){return make_pair((a.first * b.first)%p,(a.second * b.second)%q);}
pll operator + (pll a,pll b){return make_pair((a.first + b.first)%p,(a.second + b.second)%q);}
pll operator + (pll a,int b){return make_pair((a.first + b)%p,(a.second + b)%q);}
pll init(int n){
    pll ans{1ll,1ll};
    for(int i=0;i<n;i++)
        ans = ans*Tc;
    return ans;
}
pll Hash(char a[],int len){
    pll ans{0ll,0ll};
    for(int i=0;i<len;i++)
        ans = ans*Tc + a[i];
    return ans;
}
char a[maxn],b[maxn];
int main(){
    cin>>a>>b;
    int lb = strlen(b);
    pll x = Hash(b,lb), r = Hash(a,lb), l{0ll,0ll}, k_lb = init(lb);
    ll cnt = 0;
    for(int i=lb ; a[i] ; r = r*Tc + a[i],l = l*Tc + a[i-lb],i++)
        if(r - (l*k_lb) == x)
            cnt++;
    if(r - (l*k_lb) == x)cnt++;
    cout<<cnt<<endl;
    return 0;
}

注释代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pll;
const int maxn = 1e6+7;
const ll k = 1e9+9, p = 1e9+7;
const ll s = 1e9+7, q = 1e9+9;
pll Tc{k,s};///定义一个乘数,相当于单hash里的K
pll operator - (pll a,pll b){return make_pair((a.first - b.first + p)%p,(a.second - b.second + q)%q);}
///重载的时候注意到可能出现负数
pll operator * (pll a,pll b){return make_pair((a.first * b.first)%p,(a.second * b.second)%q);}
///注意取模
pll operator + (pll a,pll b){return make_pair((a.first + b.first)%p,(a.second + b.second)%q);}
pll operator + (pll a,int b){return make_pair((a.first + b)%p,(a.second + b)%q);}
///这个也可以不用,就是在加的时候注意用make_pair(b,b)代替b,然后用上面的加法就可以了
pll init(int n){///初始化那个求子串的时候 的 k的r-l+1次方
    pll ans{1ll,1ll};
    for(int i=0;i<n;i++)
        ans = ans*Tc;
    return ans;
}
pll Hash(char a[],int len){///hash一个串的指定长度
    pll ans{0ll,0ll};
    for(int i=0;i<len;i++)
        ans = ans*Tc + a[i];
    return ans;
}
char a[maxn],b[maxn];
int main(){
    cin>>a>>b;
    int lb = strlen(b);
    pll x = Hash(b,lb)///b串hash成的值
    , r = Hash(a,lb)///区间右端点
    , l{0ll,0ll}///区间左端点
    , k_lb = init(lb);///k 的 r-l+1 (lb)次方
    ll cnt = 0;
    for(int i=lb ; a[i] ; r = r*Tc + a[i],l = l*Tc + a[i-lb],i++)///r,l相应的向后乘
        if(r - (l*k_lb) == x)///如果区间等于b的hash值
            cnt++;
    if(r - (l*k_lb) == x)cnt++;///因为for少一个区间
    cout<<cnt<<endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Du_Mingm/article/details/83216090