Loading

字符串哈希

字符串哈希

其实本质上就是将字符串映射到了一个值上面,由于比较数值是 \(O(1)\) 的,所以我们可以用这种方式加快字符串的比较。

转化方法:

选择一个比字符集大小要大的数 \(p\),将每个字符串看作成 \(p\) 进制下的数,求值。

通常来说,\(p\) 会取 \(31, 131, 1313, 13131, 131313\) 等。

这里,特别要注意,求出来的值是需要取模的,因为当字符集的大小足够大或者字符串的长度足够长时,转化出来的值会很大,如果直接存下来,会逐渐丧失 \(O(1)\) 比较大小的优势。

哈希方法

  • 自然溢出方法:令 \(M = 2 ^ {64}\)\(M\) 为模数),那么,可以直接用 unsigned long long 来存储数值,因为 unsigned long long 所能表示的范围是 \(0 \sim 2 ^ {64} - 1\),刚好就是对 \(2 ^ {64}\) 取模后可能得到的余数的范围。

  • 单哈希方法:自己选择模数取模。

  • 双哈希方法:选择两个模数取模,得到两个 Hash 值,提高安全性。

洛谷 P3370 by wnsyou

获取子串的哈希值

\(n = |s|\)H(t) 表示 \(t\) 的 Hash 值, \(Hash_i\)pre(s, i) 的 Hash 值。

很明显,我们可以 \(O(n)\) 求出 \(s\) 的所有前缀的 Hash 值,那么,我们可以用一种类似于前缀和的思想得到 \(s\)\([l, r]\) 这个子串的 Hash 值。

比如说:\(H(de) = H(abcde) - H(abc) \times p ^ 2\)

那么,可以写这样一个函数来获取任意一个子串的 Hash 值:

using ull = unsigned long long;

void get_P() {
  for (int i = 1; i <= n; i++) {
    P[i] = P[i - 1] * p % M;
  }
}

ull get_Hash(int i, int j) {
  return ((Hash[j] - Hash[i - 1] * P[j - i + 1]) % M + M) % M;
}

哈希求最长回文子串

暴力

枚举每个字符作为回文串的中心,分别向左边和右边进行扩展,时间复杂度为 \(O(|s| ^ 2)\)

正解

枚举每个字符作为回文串的中心,二分左边和右边的长度,时间复杂度为 \(O(|s| \times \log |s|)\)

代码

#include <bits/stdc++.h>

using namespace std;
using ull = unsigned long long;

const int N = 1010;

string s;
int p = 131, n, ans;
ull P[N], a[N], b[N];

void get_P() {
  P[0] = 1;
  for (int i = 1; i <= n; i++) {
    P[i] = P[i - 1] * p;
  }
}

ull Hash(int l, int r, bool f) {
  if (!f) {  
    return a[r] - a[l - 1] * P[r - l + 1];
  }
  return b[l] - b[r + 1] * P[r - l + 1];
}

int main() {
  ios::sync_with_stdio(0), cin.tie(0);
  getline(cin, s), n = s.size(), s = ' ' + s;
  get_P();
  for (int i = 1; i <= n; i++) {
    a[i] = a[i - 1] * p + s[i];
  }
  for (int i = n; i >= 1; i--) {
    b[i] = b[i + 1] * p + s[i];
  }
  for (int i = 1, l, r; i <= n; i++) {
    if (i < n && s[i] == s[i + 1]) {
      l = 0, r = min(i, n - i);
      while (l < r) {
        int mid = (l + r + 1) >> 1;
        if (Hash(i - mid + 1, i, 0) == Hash(i + 1, i + mid, 1)) {
          l = mid;
        } else {
          r = mid - 1;
        }
      }
      ans = max(ans, 2 * l);
    }
    l = 0, r = min(i, n - i);
    while (l < r) {
      int mid = (l + r + 1) >> 1;
      if (Hash(i - mid, i - 1, 0) == Hash(i + 1, i + mid, 1)) {
        l = mid;
      } else {
        r = mid - 1;
      }
    }
    ans = max(ans, 2 * l + 1);
  }
  cout << ans;
  return 0;
}
posted @ 2025-05-04 10:11  Yan719  阅读(37)  评论(0)    收藏  举报