一.字符串哈希
1.前言
這是一種十分重要的算法,它能夠迅速計算出字符串裏的存在性問題,代碼也並不長,挺好寫的
2.表示方式
用數組hash[i]表示字符串i哈希之後的值,用x數組表示原字符串,則有:
hash[i] = ( hash[i - 1] * p + ( x[i] - 'a' + 1 )) % mod
p表示一個進制關係,越大越好,這樣可以避免重複。最好用unsigned long long,可以自然溢出,它主動模(2^64 - 1)
3.性質
我們這樣表示這個字符串:
x[i] = s1s2s3......si
那麼有:
x[1] = s1
x[2] = s1s2
x[3] = s1s2s3
x[4] = s1s2s3s4
其中一個字符串的表示方法就是:
sl......sr = hash[r] - hash[l - 1] * p ^ (r - l + 1)
可以通過觀察證明,好那麼字符串哈希最重要的兩個公式就介紹完了:
哈希初始化 hash[i] = ( hash[i - 1] * p + ( x[i] - 'a' + 1 )) % mod
截取字符串中一段 sl......sr = hash[r] - hash[l - 1] * p ^ (r - l + 1)
上例題QWQ
二.典例:COCI2017 Lozinke
1.題目
2.題解
這道題目在有了字符串哈希的基礎上就很簡單了吧,再說每個字符串長度不超過10.
現在來說一下詳細的實現步驟:
1.初始化哈希每個字符串
2.對哈希出來的值進行排序和去重
3.枚舉每一個字符串裏的子串,統計每個子串的出現次數
4.枚舉每個字符串,看每個字符串出現了多少次,累加出現次數減一
3.Code
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define M 20005
#define P 117
#define ULL unsigned long long
ULL p[20], lisan[M * 100], vap[M][20];
int n, len[M], tot, vis[M * 100], cnt[M * 100], ans;
char a[M][15];
ULL get_hash (int l, int r, int cur){
return vap[cur][r] - vap[cur][l - 1] * p[r - l + 1];
}
int main (){
scanf ("%d", &n);
p[0] = 1;
for (int i = 1; i <= 10; i ++)
p[i] = p[i - 1] * P;
for (int i = 1; i <= n; i ++){
scanf ("%s", a[i] + 1);
len[i] = strlen (a[i] + 1);
for (int j = 1; j <= len[i]; j ++)
vap[i][j] = vap[i][j - 1] * P + (a[i][j] - 'a' + 1);
for (int j = 1; j <= len[i]; j ++){
for (int k = j; k <= len[i]; k ++){
lisan[++ tot] = get_hash (j, k, i);
}
}
}
sort (lisan + 1, lisan + 1 + tot);
int tmp = unique (lisan + 1, lisan + 1 + tot) - lisan;
tot = tmp;
for (int i = 1; i <= n; i ++){
for (int j = 1; j <= len[i]; j ++){
for (int k = j; k <= len[i]; k ++){
int now = lower_bound (lisan + 1, lisan + 1 + tot, get_hash (j, k, i)) - lisan;//找位置,離散化
if (vis[now] != i)
vis[now] = i, cnt[now] ++;
}
}
}
for (int i = 1; i <= n; i ++){
int now = lower_bound (lisan + 1, lisan + 1 + tot, get_hash (1, len[i], i)) - lisan;
ans += cnt[now] - 1;
}
printf ("%d\n", ans);
return 0;
}