当前位置: 代码迷 >> 综合 >> zhuan [讲解] OI 字符串 常用哈希方法(by sxy sxy)
  详细解决方案

zhuan [讲解] OI 字符串 常用哈希方法(by sxy sxy)

热度:43   发布时间:2024-02-09 21:22:31.0

下面这个方法来自 算法竞赛入门经典-训练指南
方法

对于一个字符串s,令h[i] = h[i+1]*x + s[i] ,其中x是你自选的一个常数。令xp[i] = xp[i-1]*x

这样之后定义s的起点为下标i,长度为len的子串的哈希值为 h[i] - h[i+len]*xp[len] 。这个值与子串的位置,子串的内容,还有你自选的常数都有关系。哈希值使用unsigned long long(如果不是oi,可以使用int128的话当然更好) 不同子串的哈希值一定不会相同吗?不一定,但是相同的概率非常非常小。如果觉得不够保险可以分别选定两次x常数,双哈希来做。
应用

  1. 找最长公共子串 http://syzoj.com/problem/186

dp超时,后缀数组RE了?(orz Fmuckss神犇),后缀自动机不会写?来写易于理解速度也相当不错的哈希吧。

这里有两个字符串,分别给两个字符串做哈希,然后二分直接二分答案len,计算两个字符串每个起点开始长度为len的子串的哈希(O(n)的)。然后给第两个字符串的长度为len的子串的哈希值排序。枚举第一个字符串的哈希,在第二个字符串的哈希里面用lower_bound找(这也是之前给它排序的原因,排好序就可以lower_bound),找到了就说明存在这样长度的最长公共子串。于是AC代码如下(最后总共用时约1800ms,约是后缀自动机的9倍):

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <list>
#include <queue>
#include <algorithm>
using namespace std;
typedef unsigned long long ULL;
#define MAXN 200003 
ULL xp[MAXN];
ULL hasha[MAXN];
ULL hashb[MAXN];
ULL ha[MAXN];
ULL hb[MAXN];char A[MAXN], B[MAXN];
int x = 233;
int la, lb;
int mlen;
int minlen;
bool check(int len)
{for(int i = 0; i < la-len+1; i++)hasha[i] = ha[i] - ha[i+len]*xp[len];for(int i = 0; i < lb-len+1; i++)hashb[i] = hb[i] - hb[i+len]*xp[len];sort(hashb, hashb+lb-len+1);for(int i = 0; i < la-len+1; i++){ULL h = hasha[i];int p = lower_bound(hashb, hashb+lb-len+1, h)-hashb;if(p < lb-len+1 && h == hashb[p])return true;}return false;
}int main()
{scanf("%s %s", A, B);la = strlen(A);lb = strlen(B);mlen = max(la, lb);minlen = min(la, lb);for(int i = la-1; i >= 0; i--)ha[i] = ha[i+1]*x + A[i];for(int i = lb-1; i >= 0; i--)hb[i] = hb[i+1]*x + B[i];xp[0] = 1;for(int i = 1; i <= mlen; i++)xp[i] = xp[i-1]*x;int l = 0, r = minlen;int ans;while(l <= r){int m = (l+r)>>1;if(check(m)){ans = m;l = m+1;}elser = m-1;}printf("%d\n", ans);return 0;
}
  1. 出现次数超过m次的最长的子串 (uva 12206 https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&category=505&problem=3358&mosmsg=Submission+received+with+ID+18140839

最长这个条件优先,因此还是二分长度。每次检查是否存在这样一个出现次数超过m次的子串即可。将哈希值排序可以把哈希值相同的子串的哈希值排在一起,这样之后就能O(n)时间判断出超过次数是否超过m次。代码

哈希用途广泛,是个非常好的东西。

/* submition url: https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=505&page=show_problem&problem=3358 */
#include <cstdio>
#include <cstdlib>
#include <cstdarg>
#include <cstring>
#include <list>
#include <queue>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;
int m;
char str[40002];
typedef unsigned long long ULL;
ULL h[40002];
ULL hhash[40002];
ULL xp[40002];
int rrank[40002];
int x = 123;
int slen;
int pos;
bool cmp(int a, int b)
{return hhash[a] < hhash[b] || (hhash[a] == hhash[b] && a < b);
}
void calc()
{slen = strlen(str);h[slen] = 0;for(int i = slen-1; i >= 0; i--)h[i] = h[i+1]*x + str[i];xp[0] = 1;for(int i = 1; i <= slen; i++)xp[i] = xp[i-1]*x;
}
bool check(int len)
{for(int i = 0; i < slen-len+1; i++){rrank[i] = i;hhash[i] = h[i] - h[i+len]*xp[len];}sort(rrank, rrank+slen-len+1, cmp);int times = 0;pos = -1;for(int i = 0; i < slen-len+1; i++){if(i == 0 || hhash[rrank[i]] != hhash[rrank[i-1]])times = 0;if(++times >= m)pos = max(pos, rrank[i]);}return pos >= 0;
}
void solve()
{if(!check(1)){puts("none");return;}int l = 0, r = slen;int ans = 0;while(l <= r){int m = (l+r)>>1;if(check(m)){ans = m;l = m+1;}elser = m-1;}check(ans);if(ans)printf("%d %d\n", ans, pos);elseputs("none");
}
int main()
{while(true){scanf("%d", &m);if(!m)break;scanf("%s", str);calc();solve();}return 0;
}

from:http://sxysxy.org/blogs/44