【テンプレート】字符串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行每行包含一个字符串,为所提供的字符串。
输出格式:
输出包含一行,包含一个整数,为不同的字符串个数。
输入输出样例
5 abc aaaa abc abcc 12345
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值求一下,然后快乐地开始进行二分公共子串的长度
- 结束条件:
- 时间复杂度:
坑点:
上代码=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; }