Key : 咱们提供的一个要进行哈希的数字c++
\(f(x)\):即为哈希函数,将key扔到这个函数里面,能够获得Value,最核心的构造哈希表的东西算法
Hash地址:hash出来的值在哈希表中的存储位置数组
现有T组数据,每次给定两个字符串\(s1\text{和}s2\),求\(s1\text{在}s2\)中出现了几回。数据结构
首先考虑的固然是KMP了(逃ide
可是因为咱们讲的是字符串hash,那就考虑怎么用字符串hash求解;函数
考虑每次枚举每个子串的hash值,可是复杂度.....\(O(nm)\)优化
因此介绍一个优化技巧:滚动hashspa
滚动hash的诞生就是为了不在\(O(m)\)的时间复杂度内计算一个长度为m的字符串的hash值:code
咱们选取两个合适的互质常数(虽然不知道为何互质)b和h,对于字符串c,咱们搞一个hash函数:blog
\(hash(c)=(c_1b^{m-1}+c_2b^{m-2}+.....+c_mb^0)mod h\)
这个hash函数的构造过程是以递推实现的,设
\(hash(c,k)\)为前k个字符构成的子串的hash值,有
\(hash(c,k)=hash(c,k-1)\times b+c_{k}\)
为方便理解,设\(c="ABCD"\)且\(A=1,B=2....\)则
\(hash(c,2)=1\times b+2\)
\(hash(c,3)=1 \times b^2+2 \times b +3\)
\(hash(c,4)=1\times b^3+2 \times b^2+3\times b+4\)
对于c的子串\(c'=c_{k+1}c_{k+2}....c_{k+n}\),有:
\(hash(c')=hash(c,k+n)-hash(c,k)\times b^n\)
很像前缀和是否是?
也很像b进制转十进制是否是?
某位老师说过,探究新知的最好方法就是特值代入法,因此若是你们拿上面的那个例子来稍微作一下运算,就能很好地理解滚动hash这个优化方法了。
举个例子:
若是咱们想求上面那个例子的子串\("CD"\)的hash值,那么根据这个公式,就是:
\(hash("CD")=hash(4)-hash(2)\times b^2\)
而\(hash(2)\times b^2 = 1\times b^3+2\times b^2\),
因此,原式\(=3\times b+4\)
这很像咱们有一个b进制数1234要转成十进制,而上面所作的就是把1234中的12给杀掉,只留下34,再转成十进制就OK了
因此,若是咱们预处理出\(b^n\),就能够作到在\(O(1)\)的时间复杂度内get到任意子串的hash值,因此上面那道例题的时间复杂度就成功地降到了\(O(n+m)\)。
可是有些细心的同窗会发现,若是某两个子串的hash值撞车了怎么办呢?那么能够考虑double_hash,也就是将一个hash值取模两次,书本上说:能够将h分别取\(10^9+7\)和\(10^9+9\),由于他们是一对“孪生质数”,虽然我也不知道这是什么意思
(提醒:要开成unsigned long long,听说是为了天然溢出,省去取模运算)
大概就是这样子一个东西。
那这个东西有什么用呢?
假设咱们要将中国每一个人的身份证号映射到每一个人的头上
若是有一我的的身份证号xxxxxx19621011XXXX
这是一个18位数!!!!(难道你要弄一个数组存??)
通过计算,\(1390000000/10^4=13900\),即至少有13900人的身份证后四位是同样的
因此咱们能够将全部身份证后四位相同的人装到一个桶里面,这个桶的编号就是这我的身份证的后四位,这就是哈希表,主要目的就是为了解决哈希冲突,即F(key)的数值发生重复的状况。
如上面的那个身份证号,咱们能够考虑:
故,哈希表就是将\(F(key)\)做为key的哈希地址的一种数据结构。
直接定址法 :地址集合 和 关键字集合大小相同
数字分析法 :根据须要hash的 关键字的特色选择合适hash算法,尽可能寻找每一个关键字的 不一样点
平方取中法:取关键字平方以后的中间极为做为哈希地址,一个数平方以后中间几位数字与数的每一位都相关,取得位数由表长决定。好比:表长为512,=2^9,能够取平方以后中间9位二进制数做为哈希地址。
折叠法:关键字位数不少,并且关键字中每一位上的数字分布大体均匀的时候,能够采用折叠法获得哈希地址,
除留取余法:除P取余,能够选P为质数,或者不含有小于20的质因子的合数
随机数法:一般关键字不等的时候采用此法构造哈希函数较恰当。
可是这些东西貌似都是形式上的,具体怎么操做仍是得靠实现
听课的同窗里面有多少人写过图/最短路等算法呢?
图的存储有两种方法:
邻接矩阵
邻接表
在这里咱们用邻接表来实现。
void add(int a,int b,int c){ dt[cnt].from=a; dt[cnt].to=b; dt[cnt].value=c; dt[cnt].next=head[a]; head[a]=cnt++; }
这是邻接表。
void add(int a,int b){ dt[cnt].end=b; dt[cnt].next=head[a]; head[a]=cnt++; }
这是哈希表。
很像有木有???
在这里\(a,b\)是咱们用double_hash取出来的,取两个不一样的模数,两个\(F(key)\)决定一个字符串。
惟一不一样的是head数组的下标是\(key1\)。
其实要不要这么作随你。
若是咱们要遍历一个哈希表?
一样,
for(int i=head[x];i;i=dt[i].next){ ....... }
跟遍历邻接表如出一辙。
若是是一个数的话,上面讲过。(好像用离散化就好了)
若是是一个字符串的话,用前面的滚动hash就能够了。
分两种状况:
那你也不须要把\(key1\)做为head的下标了。
那就直接unsigned ll乱搞吧,天然溢出
那你须要把\(key1\)做为head的下标。
这时候你不能ull了,,那就弄那个什么孪生质数取模吧。
b记得开小一点,最好算一算。
图书馆要搞一个系统出来,支持两种操做:
add(s):表示新加入一本书名为s的书。
find(s):表示查询是否存在一本书名为s的书。
对于每一个find操做,输出一行yes或no。书名与指令之间有空格隔开,书名可能有一大堆空格,对于相同字母但大小写不一样的书名,咱们认为它是不一样的。
【样例输入】
4
add Inside C#
find Effective Java
add Effective Java
fine Effective Java
【样例输出】
no
yes
【题目分析】
这题是哈希表的一个变式,判断一个字符串是否已经出现
能够用滚动hash搞哈希表,采用double_hash
伪代码(不知道算不算):
void add(int a,int b){ ..... } int find(int a,int b){ for(int i=head[a];i;i=next[i]){ if(value[i]==b)true; } false; } int main(){ while(n--){ cin>>order; gets(s); for(i=0;i<len;i++){ key1=(key1*b1+s[i])%mod1; key2=(key2*b2+s[i])%mod2; } if(add)add(key1,key2); else{ if(find(key1,key2))yes; else no; } } }
这题还算简单。
Jbc买了一串车挂饰装扮本身,上有n个数字。它想要把挂饰扔进发动机里切成\(k\)串。若是有n mod k !=0,则最后一段小于k的能够直接舍去。并且若是有子串\((1,2,3)\)或\((3,2,1)\),Jbc就会认为这两个子串是同样的。Jbc想要多样的挂饰,因此Jbc想要找到一个合适的\(k\),使得它能获得不一样的子串最多。
例如:这一串挂饰是:\((1,1,1,2,2,2,3,3,3,1,2,3,3,1,2,2,1,3,3,2,1)\),
\(k=1\)的时候,咱们获得3个不一样的子串: $(1),(2),(3) $
\(k=2\)的时候,咱们获得6个不一样的子串: $(1,1),(1,2),(2,2),(3,3),(3,1),(2,3) $
\(k=3\)的时候,咱们获得5个不一样的子串: \((1,1,1),(2,2,2),(3,3,3),(1,2,3),(3,1,2)\)
\(k=4\)的时候,咱们获得5个不一样的子串: \((1,1,1,2),(2,2,3,3),(3,1,2,3),(3,1,2,2),(1,3,3,2)\)
【输入格式】
第一行一个整数n,第二行接n个数字。
【输出格式】
第一行2个正整数,表示能得到的最大不一样子串个数以及能得到最大值的k的个数。第二行输出全部的k。
【数据范围】
\(n\le 200000\)
\(1\le a_i\le n\)
【样例输入】
21
1 1 1 2 2 2 3 3 3 1 2 3 3 1 2 2 1 3 3 2 1
【样例输出】
6 1
2
【题目分析】
考虑最暴力的方法:
枚举k,枚举每个子串,从前日后、从后往前各扫一遍。
因此咱们就碰到了和字符串hash同样的问题:
枚举每个数复杂度有点高啊啊啊啊啊
为了不在\(O(k)\)的复杂度内枚举每个子串,咱们采用滚动hash(好像跟前面引述滚动hash的时候有点像)
预处理出正着跑的hash值以及反着跑的hash值。
枚举每个子串,将正的hash值和反的hash值乘起来。
而后再扔到set里,由于咱们知道set的特性:若是set里面有两个相同的数就会自动删除。
最后再弄一个小根堆,若是当前k可以得到当前最大值,就扔进小根堆里,不然将这个小根堆清空,再扔k。
而后呢?
没有而后了。
#include<bits/stdc++.h> #define ull unsigned long long using namespace std; ull n,a[1010101],power[1010101]; ull hash[1010101],hashback[1010101],ans=0; set<ull>ba; priority_queue<ull,vector<ull>,greater<ull> >gas; const ull b=1926; ull dash(ull i){ ba.clear(); for(ull j=1;j+i-1<=n;j+=i){ ull cas1=hash[j+i-1]-hash[j-1]*power[i]; ull cas2=hashback[j]-hashback[j+i]*power[i]; ba.insert(cas1*cas2); } return (ull)ba.size(); } int main(){ cin>>n; for(ull i=1;i<=n;i++){ cin>>a[i]; } power[0]=1; for(ull i=1;i<1000000;i++) power[i]=power[i-1]*b; for(ull i=1;i<=n;i++) hash[i]=hash[i-1]*b+a[i]; for(ull i=n;i>=1;i--) hashback[i]=hashback[i+1]*b+a[i]; /* for(ull i=1;i<=n;i++) cout<<hash[i]<<" "; cout<<endl; for(ull i=n;i;i--) cout<<hashback[i]<<" "; cout<<endl; cout<<hash[3]-hash[1]*power[2]<<" "<<b*b+b+1<<endl; cout<<hashback[n-2]-hashback[n+1]*power[3]<<endl;*/ for(ull i=1;i<=n;i++){ ull cnt=dash(i); if(cnt>ans){ ans=cnt; while(!gas.empty())gas.pop(); } if(cnt==ans)gas.push(i); } cout<<ans<<" "<<gas.size()<<endl; for(;!gas.empty();){ cout<<gas.top()<<" "; gas.pop(); } }
参考:信息学奥赛一本通 提升篇