进制转换法
s u m [ i ] = ( s u m [ i − 1 ] ∗ p + s t r [ i ] ) % m o d ( i > = 1 ) ; sum[i]=( sum[i-1]*p+str[i])\%mod (i>=1); sum[i]=(sum[i−1]∗p+str[i])%mod(i>=1);
调整方法(减少冲突):
进制
p
p
p一般取
131
131
131 或
13331
13331
13331,
m
o
d
mod
mod一般取
1
e
9
+
7
1e9+7
1e9+7或者
1
e
9
+
9
1e9+9
1e9+9;
这个也行
m
o
d
=
212370440130137957
l
l
mod = 212370440130137957ll
mod=212370440130137957ll // 是质数
因为取模是个效率极低的运算,所以我们把hash值储存在unsigned long long里面, 那样溢出时,会自动取余2的64次方,but这样可能会使2个不同串的哈希值相同,但这样的概率极低(不排除你的运气不好)。
注意:unsigned long long 数据精度很高,对格式的要求也更高,因此在做取模运算时,要注意使用一致的数据类型(哈希的时候数组都开ull)
取出字符串的子串的哈希值
在处理子串问题时,我们一般采用前缀和去维护哈希值
首先按照进制法则预处理出每一个前缀的哈希值,
s
t
r
[
i
]
str[i]
str[i]后边没必要
−
′
a
′
-'a'
−′a′,感觉
−
′
a
′
-'a'
−′a′之后哈希冲突的可能性大一些
for(int i = 1; i <= n; ++i) hash[i] = (hash[i-1] * p + str[i]) % mod
如果我们需要取出某段子串(str[l~r])的哈希值,代码如下:
inline ull getsub(int l, int r) {return hash[r] - hash[l - 1] * Pow[r - l + 1];}
也就是
h
a
s
h
[
l
hash[l
hash[l ~
r
]
=
(
h
a
s
h
[
1
r]=(hash[1
r]=(hash[1 ~
r
]
r]
r]
−
-
−
h
a
s
h
[
1
hash[1
hash[1 ~
(
l
−
1
)
]
∗
p
o
w
(
p
,
r
−
l
+
1
)
+
m
o
d
)
%
m
o
d
(l-1)] * pow(p,r-l+1)+mod)\%mod
(l−1)]∗pow(p,r−l+1)+mod)%mod
这个
P
o
w
Pow
Pow数组也是
u
l
l
ull
ull 类型的,
P
o
w
[
i
]
Pow[i]
Pow[i]表示
p
p
p 的
i
i
i 次方
64
64
64 位自然溢出的结果
预处理
p
p
p 的所有次方
Pow[0] = 1;
for(int i = 1; i <= maxn - 3; ++i)
Pow[i] = Pow[i-1] * p % mod;
助于理解取出子串的过程
hash[1] = s1
hash[2] = s1 * p p p + s2
hash[3] = s1 * p 2 p^2 p2 + s2 * p p p + s3
hash[4] = s1 * p 3 p^3 p3 + s2 * p 2 p^2 p2 + s3 * p p p + s4
hash[5] = s1 * p 4 p^4 p4 + s2 * p 3 p^3 p3 + s3 * p 2 p^2 p2 + s4 * p p p + s5
子串3~4的哈希值, H a s h Hash Hash = ( h a s h [ 4 ] − h a s h [ 2 ] ∗ p o w ( p , 4 − 3 + 1 ) =(hash[4]-hash[2]*pow(p,4-3+1) =(hash[4]−hash[2]∗pow(p,4−3+1) + + + m o d ) % m o d mod)\%mod mod)%mod
题目
求循环同构子串的数目
题意:
给定一个 T 串,然后 m 个 S 串,求这 m 个 S 串中有多少是与 T 串构造循环同构
思路:
把 T 串延长一倍,求所有长度为 n 的子串的哈希值
然后暴力即可
code:
#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const ull p = 13331;
const int maxn = 2e6 + 9;// 因为循环串,注意开2倍大小
ull Pow = 1;
ull Hash[maxn];
unordered_map <ull,int> ma;
ull getsub(int l, int r)
{
return Hash[r] - Hash[l - 1] * Pow;
}
int main()
{
ios::sync_with_stdio(false);
string t;
cin >> t;
int n = t.length();
for(int i = 1; i <= n; ++i) Pow *= p;
t = ' ' + t + t;
for(int i = 1; i < n * 2; ++i)
{
Hash[i] = Hash[i-1] * p + t[i] - 'a';
if(i >= n) ma[getsub(i - n + 1, i)] = 1;
}
int m;
cin >> m;
while(m--)
{
string s;
cin >> s;
int k = s.length(), ans = 0;
s = ' ' + s;
for(int i = 1; i <= k; ++i) Hash[i] = Hash[i-1] * p + s[i] - 'a';
for(int i = n; i <= k; ++i) ans += ma[getsub(i - n + 1, i)];
cout << ans << "\n";
}
return 0;
}
hdu 1880
哈希冲突解决办法:链地址法
学快排我感觉可以参考这个题解,所以就贴一下
输入输出巨恶心
大佬写的代码也有点抽象,勉强搞懂了
注意冲突是 x % c, 不是x, x不一定相等
题意:
每行输入一个字符串,含空格,前部分代表咒语名字,后部分代表内容
读入结束后 t 个查询
如果输入是 咒语名字,输出咒语内容
如果输入是 咒语内容,输出咒语名字
输入不存在的东西输出 what?
code:
#include<iostream>
#include<cstdio>
#include<cstring>
#define endl '\n'
using namespace std;
const int c=10000;
struct myhash //评测机上hash是关键字
{
struct node
{
long long key;//若key%c相同辅助判重
string s1;//若key相同辅助判重
string s2;//要查找的东西,字符串s1对应的字符串s2
node* next;//链表用指针
}a[c+5];// 一个链表数组,每个都可以向后插入节点
void push(long long x,string s1,string s2) //插入函数
{
if(a[x%c].key == 0) //如果数组 x % c 处没有元素,直接插入
{ // 注意冲突的是 x % c,不是 x
a[x%c].key = x;
a[x%c].s1 = s1;
a[x%c].s2 = s2;
}
else //如果有,链表插入新节点(头插入)
{
node *tmp = new node();
tmp->key = x;
tmp->s1 = s1;
tmp->s2 = s2;
tmp->next = a[x%c].next;//此处相当于head
a[x%c].next = tmp;
}
}
string found(long long x, string s1) //查找函数
{
if(a[x%c].key == x && a[x%c].s1 == s1) return a[x%c].s2;//如果字符串在数组里,直接返回对应的字符串s2
if(a[x%c].key==0) return "what?";//如果数组里没有元素,链表里肯定也没有,返回what?
else
{
node* tmp=a[x%c].next;//遍历链表查找
while(tmp!=NULL)
{
if(tmp->key == x && tmp->s1 == s1) return tmp->s2;
tmp = tmp->next;
}
return "what?";// 链表里也没有
}
}
};
myhash H1,H2;//H1存储名字对应的内容,H2存储内容对应的名字
long long to_num(string str) //前8位的乘积hash
{
long long ans = 1;
int len = str.length();
if(len > 8) len = 8;
for(int i = 0; i < len; i++) ans *= str[i];
return ans;
}
int main()
{
int t;
string str,s1,s2;
while(1)
{
int i;
getline(cin, str);//有空格,必须用getline
if(str == "@END@") break;
for(i = 0; i < str.length(); i++)
{
if(str[i] == ']') break;//找到咒语名字和内容的分界点
}
s1 = str.substr(0, i + 1);
s2 = str.substr(i + 2, str.length() - i - 1);
//将str分为两部分,s1是名字,s2是内容
H1.push(to_num(s1), s1, s2);// s1前8位的哈希值对应s2
H2.push(to_num(s2), s2, s1);// s2前8位的哈希值对应s1
}
cin >> t;
getchar();//输入一个换行符,否则换行符会被getline读入到str中,导致在哈希表中查找'\n',多输出一个"what?"
while(t--)
{
getline(cin, str);
string tmp;
if(str[0] == '[') //区分输入的是内容还是名字
tmp = H1.found(to_num(str), str);
else
tmp = H2.found(to_num(str), str);
if(tmp[0] == '[')// 输出名字不带括号
{
for(int i = 1; i < tmp.length() - 1; ++i) cout << tmp[i];
cout << endl;
}
else cout << tmp << endl;
}
return 0;
}
poj 3461
求可重叠串个数
题意:
s 串的子串中有多少 t 串
思路:
哈希一下,暴力即可
code:
#include<iostream>
#include<string>
#include<cstring>
#include<cstdio>
using namespace std;
const int N = 1e6 + 9;
typedef unsigned long long ull;
const int P = 999983;
ull p[N], hash[N];// 一个p[N]存进制的i次方 hash[N]存所求字符串的哈希值
char s[N], t[N];// t是模板串 s是所求串
int main()
{
int n;
cin >> n;
p[0] = 1;
for(int i = 1; i <= N-10; ++i)
p[i] = p[i-1] * P;
while(n--)
{
scanf("%s%s",t+1, s+1);
int l = strlen(s+1);
for(int i = 1; i <= l; ++i)
hash[i] = hash[i-1]*P + s[i];// 处理所求串的哈希值
int ll = strlen(t+1);
ull Ha = 0;
for(int i = 1; i <= ll; ++i)
Ha = Ha * P + t[i];// 求一下模板串的哈希值
int ans = 0;
for(int i = ll; i <= l; ++i)
{
//cout << i << " " << i-ll << endl;
ull x = hash[i] - hash[i-ll]*p[ll] ;
// 与模板串相同长度子串的哈希值
if(Ha == x) ans++;
}
cout << ans << endl;
}
return 0;
}
poj 2406
求最大循环次数
题意:
给定 s 串
abcd — 1
aaaa — 4
ababab — 3
看样例比较好懂题意,就是找最大的循环次数,那么肯定要尽量让循环串小
先对 s 串哈希一遍
然后从小到大枚举满足条件可以作为循环串的长度
第一个满足条件的长度的循环串就是要找的循环串,s 串长度 / 循环串长度即为答案
code:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<vector>
using namespace std;
typedef unsigned long long ull;
const int N = 1e6 + 9;
ull P = 999983;
char s[N];
ull hash[N], p[N];
int main()
{
p[0] = 1;
for(int i = 1; i <= N - 9; ++i)
p[i] = p[i-1] * P;
while(~scanf("%s", s+1))
{
if(s[1] == '.') break;
int l = strlen(s+1);
for(int i = 1; i <= l; ++i)
hash[i] = hash[i-1] * P + (ull)s[i];
// 处理s的哈希值
vector <int> v;
for(int i = 1; i <= l; ++i)// 枚举可循环字符串长度
if(l % i == 0) v.push_back(i);
int ans = 1;
for(int i = 0; i < v.size(); ++i)
{
bool f = 1;
for(int j = v[i]; j <= l; j += v[i])
{
if(hash[j] - hash[j - v[i]] * p[v[i]] != hash[v[i]])
{ // 从0开始到v[i]长度
f = 0;break;
}
}
if(f){
ans = l / v[i];break;
}
}
cout << ans << endl;
}
return 0;
}
poj 2752
求公共前后缀长度
这个题要算的是,给定一个字符串s,有哪些长度的s的前缀,同时也是s的后缀。
#include <iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define ull unsigned long long
using namespace std;
const int maxn=4e5+7;
char s[maxn];
const ull P=999983;
ull hash[maxn],p[maxn];
int main()
{
p[0]=1;
for(int i=1;i<=maxn-3;++i)
p[i]=p[i-1]*P;
while(~scanf("%s",s+1))
{
int len=strlen(s+1);
for(int i = 1;i <= len; ++i)
hash[i] = hash[i-1] * P + (ull)s[i];
vector<int> v;
for(int i = 1;i <= len; ++i)
{
if(hash[i] == hash[len]-hash[len-i]*p[i])
v.push_back(i);
}
for(int i = 0; i < v.size(); ++i)
{
cout << v[i];
if(i!=v.size() - 1) cout << " ";
}
printf("\n");
}
return 0;
}
K - Unique Activities
题意:
给定长度为
N
N
N 的字符串,找子串中出现次数仅为一次,并且长度最短的子串,如果有多个,输出从左往右第一个出现的子串。
思路:
首先想到暴力算法,先枚举长度,然后模拟求答案
枚举长度会
T
L
E
TLE
TLE ,优化时间我们可以选择二分长度
一开始想着
u
n
o
r
d
e
r
e
d
unordered
unordered_
m
a
p
<
s
t
r
i
n
g
,
i
n
t
>
map<string,int>
map<string,int>,但是由于
s
t
r
i
n
g
string
string 的存在,会
M
L
E
MLE
MLE,所以我们可以采用哈希优化,把字符串映射成数值,也就变成了
u
n
o
r
d
e
r
e
d
unordered
unordered _
m
a
p
<
u
l
l
,
i
n
t
>
map<ull,int>
map<ull,int>,这时就不会
M
L
E
MLE
MLE 了。
code:
#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define epc 1e-5
using namespace std;
const int maxn = 3e5 + 9;
const int mod = 1e9 + 7;
ll n, m, k;
ull Pow[maxn];
ull P = 13331;
ull Hash[maxn];
ull getHash(int l, int r)
{
return Hash[r] - Hash[l-1] * Pow[r - l + 1];
}
string s;
unordered_map <ull, int> ma;
bool check(int l)
{
ma.clear();
for(int i = 0; i + l - 1 < s.size(); ++i)
{
ull ans = getHash(i+1, i + l);
++ma[ans];
}
for(auto x : ma)
if(x.second == 1){
return 1;
}
return 0;
}
void work()
{
cin >> s;
n = s.size();
for(int i = 1; i <= n; ++i)
Hash[i] = Hash[i-1] * P + s[i-1];
int len = 0;
int r = s.size(), l = 1;
while(l < r)
{
int mid = (r + l) >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
//cout << l << endl;
len = l;
ma.clear();
for(int i = 0; i < s.size(); ++i)
{
ull ans = getHash(i+1, i + len);
++ma[ans];
}
for(int i = 0; i < s.size(); ++i)
{
ull ans = getHash(i+1, i + len);
string t = "";
t += s[i];
int j = i;
while(j < s.size() && j - i + 1 < len) ++j, t += s[j];
if(ma[ans] == 1){
cout << t << endl;return;
}
}
}
int main()
{
ios::sync_with_stdio(0);
//int TT;cin>>TT;while(TT--)
Pow[0] = 1;
for(int i = 1; i <= maxn - 9; ++i)
Pow[i] = Pow[i-1] * P;
work();
return 0;
}