( 字符串专题 )【 字符串hash 】
字符串Hash可以通俗的理解为,把一个字符串转换为一个整数。
如果我们通过某种方法,将字符串转换为一个整数,就可以便的确定某个字符串是否重复出现过,这是最简单的字符串Hash应用情景了。
单Hash方法
unsigned long long Hash[n];
char s[200005];
for ( int i=1; i<=n; i++ ) {
hash[i] = hash[i−1]*p + s[i];
}
利用unsigned long long的范围自然溢出,相当于自动对pow(2,64)-1 取模,无需再手动取模
获取子串的Hash
如果我们求出一个串的Hash,就可以O(1)求解其子串的Hash值。
用 unsigned long long 自动取模的话无需担心。
例题1:E、Sort String
题目链接:https://ac.nowcoder.com/acm/problem/17244
题意:给一个长度为len的字符串,头尾相连,定义Si为以 i 为开头的长度为len的字符串。相同的字符串分到一组里,输出len个字符串分成了几组,输出每一组的下标 i ,按照从小到大的顺序。
比如: abca -> abca , bcaa , caab , aabc
样例:
输入 abab
输出
2
2 0 2
2 1 3
思路:用字符串hash来做,可以O(n)的求出长度为len的len个字符串的hash值,然后用map和vector来处理答案就可以了。
注意:数据比较大,用快输出,用printf 超时了。用unordered_map , 否则超时
代码:
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
ull base = 233317;
ull pwd[2000006]; // 存hash值
char s[2000006];
vector<int> ans[2000006];
unordered_map<ull,int> mp;
int cnt;
inline void write( int x )
{
if ( x<0 ) putchar('-'),x=-x;
if ( x>9 ) write(x/10);
putchar(x%10+'0');
}
int main()
{
scanf("%s",s+1); s[0] = '#';
int len = strlen(s)-1;
pwd[0] = 0;
for ( int i=1; i<=len; i++ ) s[i+len] = s[i];
for ( int i=1; i<=len; i++ ) pwd[i] = pwd[i-1]*base+s[i]; // 先处理出前len项的
ull base_n = 1;
for ( int i=1; i<len; i++ ) base_n*=base; // 两个字符距离差了len所差的倍数
for ( int i=len+1; i<=len+len; i++ ) {
pwd[i] = (pwd[i-1] - s[i-len]*base_n)*base+s[i]; // pwd存的不是前len项了, 而是从当前i往前数len个的hash值。
}
int cnt=1;
for ( int i=len; i<len*2; i++ ) { // 将长度为len的字符串都化成了一个数pwd[i], 比较数是否相同就行了
if ( mp[pwd[i]]==0 ) {
mp[ pwd[i] ] = cnt++;
ans[ cnt-1 ].push_back(i-len);
}
else ans[ mp[pwd[i]] ].push_back(i-len);
}
printf("%d\n",cnt-1);
for ( int i=1; i<cnt; i++ ) {
int len = ans[i].size();
write(len);printf(" ");
for ( int j=0; j<len-1; j++ ) {
write(ans[i][j]); // 快输,用printf超时
printf(" ");
}
write(ans[i][len-1]);printf("\n");
}
return 0;
}
例题2:Gym-100783J
题目链接:https://vjudge.net/problem/Gym-100783J
题意:在大图里找有几个小图。
思路:二维hash, 具体思路看代码。
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
ull base=233317; //or 827
ull base2=200379; //or 1127
ull pwd[2005];
char s[2005][2005], t[2005][2005];
int n,m,N,M;
int main()
{
cin >> n >> m >> N >> M;
for ( int i=1; i<=n; i++ ) scanf("%s",t[i]+1);
for ( int i=1; i<=N; i++ ) scanf("%s",s[i]+1);
ull want = 0;
for ( int i=1; i<=n; i++ ) {
ull tmp = 0;
for ( int j=1; j<=m; j++ ) {
tmp = tmp*base+t[i][j];
}
want = want*base2+tmp;
}
for ( int i=1; i<=N; i++ ) {
pwd[i] = 0;
for ( int j=1; j<=m; j++ ) {
pwd[i]=pwd[i]*base+s[i][j];
}
}
ull base_n=1, base_m=1;
for ( int i=1; i<n; i++ ) base_n=base_n*base2;
for ( int i=1; i<m; i++ ) base_m=base_m*base;
int ans=0;
for ( int j=1; j<=M-m+1; j++ ) {
ull cur=0;
for ( int i=1; i<n; i++ ) cur=cur*base2+pwd[i];
for ( int i=n; i<=N; i++ ) {
cur = (cur-pwd[i-n]*base_n)*base2+pwd[i];
if ( cur==want ) ans++;
}
for ( int i=1; i<=N; i++ ) {
pwd[i] = (pwd[i]-s[i][j]*base_m)*base+s[i][j+m];
}
}
cout << ans << endl;
return 0;
}