字符串哈希

  • 使用自然溢出时数据量不应太大,因为要用到map,(数组不够开ull!),map会自动排序,所以会超时。所以超过5*10^5就别用自然溢出了,找到最大可能值即可。
  • 一般乘以素数p(131,13331,2333等)或最大值,然后%素数1e9+7

字符串哈希

即将一个字符串转化成一个整数,并保证字符串不同,得到的哈希值不同,这样就可以用来判断一个该字串是否重复出现过。

(如果直接把string当做键,则每次在map中查找时要一个一个字符地找,跟存在数组中每区别,比较数值当然更快。)

由哈希函数的性质,对于一个字符串:S=s1s2...sn,我们把每个字符转换成idx(si)=si-'a'+1 当然直接用字符串的ASCII码表示也可以,则哈希模型为Hash(i)=Hash(i-1)*p+idx(si),其中p为素数。最终算出的Hash(n)作为该字符串的哈希值。所以构造哈希函数的关键点在于使不同字符串的哈希冲突率尽可能小。下面介绍几种哈希函数:

哈希方法

  • 自然溢出方法:利用unsigned long long 自然溢出,相当于自动对2^64−1取模

 定义哈希函数:unsigned long long hash[n];

公式:Hash[i] = Hash[i-1]*p+idx(si)

  • 单哈希方法

公式:Hash[i] = (Hash[i-1]*p+idx(si))%mod

p,mod均为质数,p<mod,p,mod取尽量大时冲突很小

  • 双哈希方法:将字符串用不同mod单哈希两次,结果用二元组表示

Hash1[i] = (Hash1[i-1]*p+idx(si))%mod

Hash2[i] = (Hash2[i-1]*p+idx(si))%mod

Hash[i]:<Hash1[i],Hash2[i]>

这种方法很安全

求出一个串的哈希值后得到字串的哈希值:o(1)

素数的选择

像1e9+7等常见素数很可能被出题人卡,所以可以选择一些其他的素数,像2333等

下面求两个串中同字母异构的个数,不关心排列顺序,所以哈希时从a~z遍历取个数。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define far(i,t,n) for(int i=t;i<n;++i)

char a[4050],b[4050];
map<ull,ll>hashS;
map<ull,ll>::iterator it;
ull cnt[30];
ull p=1e9+7;

ull getHash()
{
    ull ans=26;
    far(i,0,26)
    {
        ans*=p;
        ans+=cnt[i];
    }
    return ans;
}

int main()
{
    scanf("%s%s",a,b);
    int la=strlen(a),lb=strlen(b);
    int l=min(la,lb);
    int ans=0;
    for(int i=1;i<=l;++i)
    {
        hashS.clear();
        memset(cnt,0,sizeof(cnt));
        for(int j=0;j<i;j++)
            cnt[a[j]-'a']++;
        for(int j=0;i+j<la;++j)
        {
            hashS[getHash()]++;
            cnt[a[j]-'a']--;
            cnt[a[i+j]-'a']++;
        }

        hashS[getHash()]++;
        memset(cnt,0,sizeof(cnt));
        for(int j=0;j<i;j++)
            cnt[b[j]-'a']++;
        for(int j=0;i+j<lb;++j)
        {
            if(hashS[getHash()])
                ans=max(ans,i);
            cnt[b[j]-'a']--;
            cnt[b[i+j]-'a']++;

        }
        if(hashS[getHash()])ans=max(ans,i);
    }
    printf("%d\n",ans);
    return 0;
}

 如果需要算出一个字符串长度为n的所有子串,则提前预处理出0~i的哈希值hash[i],则可以以0(1)的复杂度算出[l,r]的哈希值

#include<bits/stdc++.h>
using namespace std;
#define far(i,t,n) for(int i=t;i<n;++i)
typedef long long ll;
typedef unsigned long long ull;

char t[1000010];
char w[10010];
const int p=12582917;
ull po[10010];
ull Hash[1000010];

void initPo()//预处理p^(r-l+1)
{
    po[0]=1;
    po[1]=p;
    far(i,2,10002)
        po[i]=po[i-1]*p;
}

ull getSingleHash(char c[],int n)
{
    Hash[0]=c[0]-'A';
    far(i,1,n)
    {
        Hash[i]=Hash[i-1]*p;
        Hash[i]+=c[i]-'A';
    }
}

ull getHash(int l,int r)
{
    if(l==0)
        return Hash[r];
    return Hash[r]-Hash[l-1]*po[r-l+1];
}

int main()
{
    int Kase;
    cin>>Kase;
    initPo();
    while(Kase--)
    {
        scanf("%s%s",w,t);
        int lw=strlen(w),lt=strlen(t);
        ull x=w[0]-'A';
        far(i,1,lw)
            x=x*p+(w[i]-'A');
        getSingleHash(t,lt);
        int ans=0;
        for(int i=0;i<=lt-lw;++i)
        {
           // cout<<i<<" "<<getHash(i,i+lw)<<endl;
            if(x==getHash(i,i+lw-1))
                ++ans;
        }

        printf("%d\n",ans);
    }

    return 0;
}

参考:https://blog.csdn.net/pengwill97/article/details/80879387

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值