【テンプレート】字符串hash

不懂hash是什么的盆友给出直通车:滴滴滴,开车啦~

如果你看懂了的话:

hash模板来也~

#include <cstdio>
#include <string>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 1e5 + 6;  //100006
const int D = 131;      //质数 
const int MD = 1e9 + 7; //大质数 

int n;
string s;

unsigned long long f[N], g[N]; //g[i]为D的i次方 

void prehash(int n) //进行预处理 
{
// 预处理时,注意到数字可能很大,对一个数 MD 取模
    f[0] = s[0];//预处理前缀和(进行强制类型转) 
    for(int i=1; i<=n; i++)
        f[i] = (1LL * f[i-1] * D + s[i-1]) % MD;

    g[0] = 1; //预处理D 
    for(int i=1; i<=n; i++)
        g[i] = 1LL * g[i-1] * D % MD;
}

int hash(int l, int r) //计算区间 [l,r] 的哈希值
{
    int a = f[r];
    int b = 1LL * f[l-1] * g[r-l+1] % MD;

    return (a - b + MD) % MD; //+MD是为了防止出现负数 
}

int main()
{
    cin >> s;
    n = s.length();
    prehash(n);

    while(!cin.eof())//直至按ctrl+z键退出!
    {
        int l, r;
        cin >> l >> r;
        cout << s.substr(l-1, r-l+1) << ": " << hash(l, r) << endl;
        //从l-1到r-l+1的字符 
    }

    return 0;
}

良心推荐洛谷练手题:

1.P3370【模板】字符串哈希

题目描述

如题,给定N个字符串(第i个字符串长度为Mi,字符串内包含数字、大小写字母,大小写敏感),请求出N个字符串中共有多少个不同的字符串。

友情提醒:如果真的想好好练习哈希的话,请自觉,否则请右转PJ试炼场:)

输入输出格式

输入格式:

第一行包含一个整数N,为字符串的个数。

接下来N行每行包含一个字符串,为所提供的字符串。

输出格式:

输出包含一行,包含一个整数,为不同的字符串个数。

输入输出样例

输入样例#1:
5
abc
aaaa
abc
abcc
12345
输出样例#1:
4

说明

时空限制:1000ms,128M

数据规模:

对于30%的数据:N<=10,Mi≈6,Mmax<=15;

对于70%的数据:N<=1000,Mi≈100,Mmax<=150

对于100%的数据:N<=10000,Mi≈1000,Mmax<=1500

样例说明:

样例中第一个字符串(abc)和第三个字符串(abc)是一样的,所以所提供字符串的集合为{aaaa,abc,abcc,12345},故共计4个不同的字符串。

Tip: 感兴趣的话,你们可以先看一看以下三题:

BZOJ3097:http://www.lydsy.com/JudgeOnline/problem.php?id=3097

BZOJ3098:http://www.lydsy.com/JudgeOnline/problem.php?id=3098

BZOJ3099:http://www.lydsy.com/JudgeOnline/problem.php?id=3099

如果你仔细研究过了(或者至少仔细看过AC人数的话),我想你一定会明白字符串哈希的正确姿势的^_^

思路:

  题解里面是有很多种方法的,但是我认为能够搞懂一种就已经很不错了……

  所以我给出的思路就是用set来进行统计,set只能够允许一种数字出现一次,所以我们就可以将给出的字符串的hash值求出来

  然后加入到set里面,最后直接输出set里面有多少数就行啦~

PS:

      定义函数的时候不要用"hash"这个名字,在洛谷里是关键字!!!

上代码=u=:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
#include <cstring>
#include <set>

using namespace std;

typedef unsigned long long ull;
typedef long long LL;
set<ull>ssr;

const int N = 2333;
const int D = 131;     //质数 
const ull MD= 1e9 + 7; //大质数 

LL n,len;
string s;
 
ull f[N];//预处理前缀和 
ull g[N];//预处理 D的i次方 

int yuchuli(int q)
{
    memset(f,0,sizeof(f));
    memset(g,0,sizeof(g));
    f[0]=s[0];
    for(int i=1;i<=q;i++)
        f[i] = (1LL * f[i-1] * D +s[i-1]) % MD;
    g[0]=1;
    for(int i=1;i<=q;i++)
        g[i] = 1LL * g[i-1] * D % MD;
}

int hashs(int l,int r)
{
    ull a = f[r];
    ull b = 1LL * f[l-1] *g[r-l+1] % MD;

    return (a - b + MD) % MD;
}

int main()
{
    cin>>n;
    while(n--)
    {
        cin>>s;
        len=s.length();
        yuchuli(len);
        ull qq = hashs(1,len);
        ssr.insert(qq);        
    }
    cout<<ssr.size();
    return 0;
}  

2.

cogs249. [POI2000] 最长公共子串

★★★   输入文件:pow.in   输出文件:pow.out   简单对比
时间限制:1 s   内存限制:256 MB

给出几个由小写字母构成的单词,求它们最长的公共子串的长度。

任务

  • 从文件中读入单词
  • 计算最长公共子串的长度
  • 输出结果到文件

输入

文件的第一行是整数 n,1<=n<=5,表示单词的数量。接下来n行每行一个单词,只由小写字母组成,单词的长度至少为1,最大为2000。

输出:

仅一行,一个整数,最长公共子串的长度。

样例输入:

3
abcb
bca
acbc

样例输出:

2

 思路:

  正解据说是后缀数组呢!后缀数组是什么鬼???我又不会...只好胡乱搞hash啦~结果一搞...奇迹的就WA了一个点,结果又胡乱改了一下Mod,然后就A了...真不可思议w

    Ps:脑洞产物(正解xxx),能不能过全看 your rp!!!

  话不多说:

  hash的思路:

  •       pre:

          首先话不多说,跟普通hash一样把每一个单词的hash值求一下,然后快乐地开始进行二分公共子串的长度

          然后我们又设置了vis和times两个数组,其中times[i]表示当前hash值i已经出现在几个单词里面。
          接着枚举每个单词中子串的开始位置,用O(1)时间求该子串的hash,然后times加一。
          (当然同一个长单词里面可能有相同的子串,所以用vis记录一下这个hash值最后出现在第几个单词中,来判断该子串在当前字符串中是不是第一次出现。)
  •       结束条件:
          如果times加到n了,说明每个单词里面都有这个子串,这个解是正确的,然后顺路更新一下ans的值!
  •       时间复杂度:
          1)求hash是O(|S|*n),|S|表示最长的子串长度。
          2)二分答案部分为(log|S|*n*|S|)。
        故总体的期望复杂度为O(log|S|*|S|*n),而n只有5,|S|只有2000,显然轻松过去..
        over!!!

坑点:

   在选取Mod以及D的时候,一定要多用几组,防止发生冲突。

上代码=v=:

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#define INF 0x7fffffff
#define LL long long
using namespace std;

const int D = 20021;
const int Mod = 10000009;
const int N = 6;
const int le = 2020;
int n,minl,ans;
LL f[N][le],g[le];
int len[N];
char str[le];
int vis[Mod+1],times[Mod+1];

bool check(int x)
{
    memset(vis,0,sizeof(vis));
    memset(times,0,sizeof(times));
    LL cv=g[x];
    for(int i=1;i<=n;++i)
    {
        for(int j=x;j<=len[i];++j)
        {
            LL nowhash=(f[i][j]-(f[i][j-x]*cv)%Mod+Mod)%Mod;
            if(vis[nowhash]!=i)
            {
                ++times[nowhash];
                vis[nowhash]=i;
                if(times[nowhash]==n)
                    return true;
            }
        }
    }
    return false;
}

void prehash(int i)
{
    f[i][1]=str[0]-'a'+1;
    for(int j=2;j<=len[i];++j)
        f[i][j]=(f[i][j-1]*D+(str[j-1]-'a'+1))%Mod;
}

void dichotomy()
{
    int l=0,r=minl+1;
    while(l<r)
    {
        int mid=(l+r)>>1;
        if(check(mid))
            ans=mid,l=mid+1;
        else
            r=mid;
    }
    printf("%d",ans);
}

int main()
{
    freopen("pow.in", "r", stdin);
    freopen("pow.out", "w", stdout);
    scanf("%d",&n);
    minl=INF;
    for(int i=1;i<=n;++i)
    {
        scanf("%s",str);
        len[i]=strlen(str);
        if(len[i]<minl) minl=len[i];
        prehash(i);
    }
    g[0]=1;
    for(int i=1;i<=minl;++i)
        g[i]=1LL*g[i-1]*D%Mod;    
    dichotomy();
    return 0;
}

 

 

转载于:https://www.cnblogs.com/zxqxwnngztxx/p/6853369.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值