array(2) { ["docs"]=> array(10) { [0]=> array(10) { ["id"]=> string(3) "428" ["text"]=> string(77) "Visual Studio 2017 单独启动MSDN帮助(Microsoft Help Viewer)的方法" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(8) "DonetRen" ["tagsname"]=> string(55) "Visual Studio 2017|MSDN帮助|C#程序|.NET|Help Viewer" ["tagsid"]=> string(23) "[401,402,403,"300",404]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511400964" ["_id"]=> string(3) "428" } [1]=> array(10) { ["id"]=> string(3) "427" ["text"]=> string(42) "npm -v;报错 cannot find module "wrapp"" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(4) "zzty" ["tagsname"]=> string(50) "node.js|npm|cannot find module "wrapp“|node" ["tagsid"]=> string(19) "[398,"239",399,400]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511400760" ["_id"]=> string(3) "427" } [2]=> array(10) { ["id"]=> string(3) "426" ["text"]=> string(54) "说说css中pt、px、em、rem都扮演了什么角色" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(12) "zhengqiaoyin" ["tagsname"]=> string(0) "" ["tagsid"]=> string(2) "[]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511400640" ["_id"]=> string(3) "426" } [3]=> array(10) { ["id"]=> string(3) "425" ["text"]=> string(83) "深入学习JS执行--创建执行上下文(变量对象,作用域链,this)" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(7) "Ry-yuan" ["tagsname"]=> string(33) "Javascript|Javascript执行过程" ["tagsid"]=> string(13) "["169","191"]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511399901" ["_id"]=> string(3) "425" } [4]=> array(10) { ["id"]=> string(3) "424" ["text"]=> string(30) "C# 排序技术研究与对比" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(9) "vveiliang" ["tagsname"]=> string(0) "" ["tagsid"]=> string(2) "[]" ["catesname"]=> string(8) ".Net Dev" ["catesid"]=> string(5) "[199]" ["createtime"]=> string(10) "1511399150" ["_id"]=> string(3) "424" } [5]=> array(10) { ["id"]=> string(3) "423" ["text"]=> string(72) "【算法】小白的算法笔记:快速排序算法的编码和优化" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(9) "penghuwan" ["tagsname"]=> string(6) "算法" ["tagsid"]=> string(7) "["344"]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511398109" ["_id"]=> string(3) "423" } [6]=> array(10) { ["id"]=> string(3) "422" ["text"]=> string(64) "JavaScript数据可视化编程学习(二)Flotr2,雷达图" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(7) "chengxs" ["tagsname"]=> string(28) "数据可视化|前端学习" ["tagsid"]=> string(9) "[396,397]" ["catesname"]=> string(18) "前端基本知识" ["catesid"]=> string(5) "[198]" ["createtime"]=> string(10) "1511397800" ["_id"]=> string(3) "422" } [7]=> array(10) { ["id"]=> string(3) "421" ["text"]=> string(36) "C#表达式目录树(Expression)" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(4) "wwym" ["tagsname"]=> string(0) "" ["tagsid"]=> string(2) "[]" ["catesname"]=> string(4) ".NET" ["catesid"]=> string(7) "["119"]" ["createtime"]=> string(10) "1511397474" ["_id"]=> string(3) "421" } [8]=> array(10) { ["id"]=> string(3) "420" ["text"]=> string(47) "数据结构 队列_队列实例:事件处理" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(7) "idreamo" ["tagsname"]=> string(40) "C语言|数据结构|队列|事件处理" ["tagsid"]=> string(23) "["246","247","248",395]" ["catesname"]=> string(12) "数据结构" ["catesid"]=> string(7) "["133"]" ["createtime"]=> string(10) "1511397279" ["_id"]=> string(3) "420" } [9]=> array(10) { ["id"]=> string(3) "419" ["text"]=> string(47) "久等了,博客园官方Android客户端发布" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(3) "cmt" ["tagsname"]=> string(0) "" ["tagsid"]=> string(2) "[]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511396549" ["_id"]=> string(3) "419" } } ["count"]=> int(200) } 222 哈希、哈希表详解及应用 - 爱码网
ironwheel

前置概念

Key : 我们提供的一个要进行哈希的数字

\(f(x)\):即为哈希函数,将key扔到这个函数里面,可以得到Value,最核心的构造哈希表的东西

Hash地址:hash出来的值在哈希表中的存储位置

进入正题

字符串hash

例题1:【模板】KMP

现有T组数据,每次给定两个字符串\(s1\text{和}s2\),求\(s1\text{在}s2\)中出现了几次。

首先考虑的当然是KMP了(逃

但是由于我们讲的是字符串hash,那就考虑怎么用字符串hash求解;

考虑每次枚举每一个子串的hash值,但是复杂度.....\(O(nm)\)

所以介绍一个优化技巧:滚动hash

滚动hash

滚动hash的诞生就是为了避免在\(O(m)\)的时间复杂度内计算一个长度为m的字符串的hash值:

我们选取两个合适的互质常数(虽然不知道为什么互质)b和h,对于字符串c,我们搞一个hash函数:

\(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的质因子的合数

随机数法:通常关键字不等的时候采用此法构造哈希函数较恰当。

但是这些东西貌似都是形式上的,具体怎么操作还是得靠实现

哈希表的实现

听课的同学里面有多少人写过图/最短路等算法呢?

图的存储有两种方法:

  1. 邻接矩阵

  2. 邻接表

在这里我们用邻接表来实现。

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表中hash函数的确定

如果是一个数的话,上面讲过。(好像用离散化就行了)

如果是一个字符串的话,用前面的滚动hash就可以了。

分两种情况:

如果你不想用double_hash:

那你也不需要把\(key1\)作为head的下标了。

那就直接unsigned ll乱搞吧,自然溢出

如果你要用double_hash:

那你需要把\(key1\)作为head的下标。

这时候你不能ull了,,那就弄那个什么孪生质数取模吧。

b记得开小一点,最好算一算。

例题2:图书管理

图书馆要搞一个系统出来,支持两种操作:

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;
		}
    }
}

这题还算简单。

例题3 [LuoguP3498&POI2010]Beads

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();
    }
    
}

讲完了

祝大家身体健康

参考:信息学奥赛一本通 提高篇

分类:

技术点:

相关文章: