今天想学字符串hash是怎么弄的。就看到了这题模板题

http://acm.hdu.edu.cn/showproblem.php?pid=4622

刚开始当然不懂啦,然后就上网搜解法。很多都是什么后缀自动机那些。作为小白的我当然不懂啦,更重要的是我想学的是字符串hash这种解法呢?然而有这种解法,但是却都是只有代码,看起来很辛苦。所以这里我把我的理解写上来,当然有错误的话,请各路高手指出来,我也好好学习下~~

首先介绍一个字符串Hash的优秀映射函数:BKDRHash,这里hash一开始是等于0的

for(i=1 to lenstr)  hash = seed * hash + str[i]; 这是求解hash值的公式。绝大多数情况下能唯一确定字符串。seed是一个参数,一般取 31 、131、 1313、 13131、 131313、 etc..冲突比较小

经典题目:HDU 4622 Reincarnation

题意:给定一个长为2000个字符串,给出Q(Q<=10000)个询问。每个询问包含[L,R],要求算出这个区间内不同的子串的个数。

思路:暴力枚举区间长度L,从1开始枚举到lenstr,再枚举起点i即可。能在O(n2)的时间枚举完。但仅仅是枚举完,但这里并没有去重,这部分时间,我们用hash来完成,复杂度压到O(1)。什么叫去重呢?例如baba,当我们枚举第二个ba的时候,就要告诉我们”ba”在[1,4]中重复出现了一次,所以ans[1][4]--; //ans[L][R]就是表示区间内不同子串的个数了。

要枚举那么多子串,我们希望,对于任意给定的区间[L,R],都能快速地算出它的hash值是多少。例如求[3,4]的hash值,明显有 ans = seed * str[3] + str[4];(这是根据公式得到的。

那么我们先预处理一个前缀hash总和,记为sumHash[i]表示1~i的hash值。则有

sumHash[1] = str[1];                                            sunHash[2] = seed * str[1] + str[2];

sunHash[3] = seed * sumHash[2] + str[3];     sumHash[4] = seed * sumHash[3] + str[4];

把他们拆出来,即可得到[3,4]    ans = sumHash[4]  –  seed(R-L+1)  *  sumHash[2];

所以预处理两个数组,powseed[i]表示seed的i次方, sumHash[i]定义如上

然后就是怎么判断重复出现的问题了。我们知道那个hash值是唯一的,我们只能靠这个来判断是否重复出现,但是这个hash值很大,用map<ULL,int>来模拟是可以得,但是很慢。怎么办呢?我们可以用图,先把hash值%MOD压缩下,把他们加入到一幅图中,再开一个数组保存边的权值,用边的权值来和hash值判断相不相同,即可确定是否重复出现。

那个图没什么了不起的,就是为了返回出现的位置,不要被那个图吓到了。

下面代码可以300+ms

#include <cstdio>
#include <cstdlib>
#include <cstring>
typedef unsigned long long int ULL;
//BKDRHash,最优的字符串hash算法。hash一开始是等于0的
//for(i=1 to lenstr) hash = seed * hash + str[i]; 这是求解hash值的公式
//我们希望,对于任意给定的区间[L,R],都能快速地算出它的hash值是多少
//明显我们要算的是:例如[3,4] ans = seed * str[3] + str[4]
//那么我们先预处理一个前缀hash总和,记为sumHash[i]表示1~i的hash值
//sumHash[1] = str[1]; sunHash[2] = seed * str[1] + str[2];
//sunHash[3] = seed * sumHash[2] + str[3]; sumHash[4] = seed * sumHash[3] + str[4];
//拆出来,得到若要求[3,4] 既可以 ans = sumHash[4] - seed^(R-L+1) * sumHash[2]
//所以预处理两个数组,powseed[i]表示seed的i次方, sumHash[i]定义如上
const int seed = ; // 31 131 1313 13131 131313 etc..
const int maxn = +;
char str[maxn];
ULL powseed[maxn]; // seed的i次方 爆了也没所谓,sumHash的也爆。用了ULL,爆了也没所谓,也能唯一确定它,无符号
ULL sumHash[maxn]; //前缀hash值
int ans[maxn][maxn]; //ans[L][R]就代表ans,就是区间[L,R]内不同子串的个数
const int MOD = ;
struct StringHash
{
int first[MOD+],num; // 这里因为是%MOD ,所以数组大小注意,不是maxn
ULL EdgeNum[maxn]; // 表明第i条边放的数字(就是sumHash那个数字)
int next[maxn],close[maxn]; //close[i]表示与第i条边所放权值相同的开始的最大位置
//就比如baba,现在枚举长度是2,开始的时候ba,close[1] = 1;表明"ba"开始最大位置是从1开始
//然后枚举到下一个ba的时候,close[1]就要变成3了,开始位置从3开始了
void init ()
{
num = ; memset (first,,sizeof first);
return ;
}
int insert (ULL val,int id) //id是用来改变close[]的
{
int u = val % MOD; //这里压缩了下标,val是一个很大的数字,这里就有一个问题了,val是唯一的,因为它是从sumHash得到的
//那个hash算法很优秀,基本上val是唯一的了。现在我们想知道和val值相同的地方是哪里。又是上面那个例子了。baba。当我们
//枚举第二个ba的时候,我想知道它有没出现过,如果有,请放回它出现的位置。这里其实完全可以用map<ULL,int>book这样做,
//如果book[val] != 0,就代表出现过了,更新,返回就可以。但是非常慢,2800+ms,第二次提交还TLE
//所以我们逼不得已用图了,再加上其他辅助的数组.EdgeNum[]就是用来判断和val相不相同的。这样时间才降下来
for (int i = first[u]; i ; i = next[i]) //存在边不代表出现过,出现过要用val判断,val才是唯一的,边还是压缩后(%MOD)的呢
{
if (val == EdgeNum[i]) //出现过了
{
int t = close[i]; close[i] = id;//更新最大位置
return t;
}
}
++num; //没出现过的话,就加入图吧
EdgeNum[num] = val; // 这个才是精确的
close[num] = id;
next[num] = first[u];
first[u] = num;
return ;//没出现过
}
}H;
void work ()
{
scanf ("%s",str+);
int lenstr = strlen(str+);
for (int i=;i<=lenstr;++i)
sumHash[i] = sumHash[i-]*seed + str[i];
memset(ans,,sizeof(ans));
for (int L=;L<=lenstr;++L) //暴力枚举子串长度
{
H.init();
for (int i=;i+L-<=lenstr;++i)
{
int pos = H.insert(sumHash[i+L-]-powseed[L]*sumHash[i-],i);
ans[i][i+L-] ++;//ans[L][R]++,自己是一个
ans[pos][i+L-]--;//pos放回0是没用的
//就像bababa,第二个ba的时候,会ans[1][4]--;表明[1,4]重复了一个
//然后第三个ba的时候,ans[2][6]--,同理,表明[2,6]也是重复了
//那么ans[1][6]重复了两个怎么算?就是在递推的时候,将ans[2][6]的值覆盖上来的
//ans[1][6] += ans[2][6] + ans[1][5] - ans[2][5];
}
}
for (int i = lenstr; i>=; i--)
{
for (int j=i;j<=lenstr;j++)
{
ans[i][j] += ans[i+][j]+ans[i][j-]-ans[i+][j-];
}
}
int m;
scanf ("%d",&m);
while (m--)
{
int L,R;
scanf ("%d%d",&L,&R);
printf ("%d\n",ans[L][R]);
}
return ;
} int main ()
{
powseed[] = ;
for (int i = ; i <= maxn-; ++i) powseed[i] = powseed[i-] * seed;
int t;
scanf ("%d",&t);
while (t--) work();
return ;
}

下面再附上一个用map模拟的代码。2800+ms,可能会超时哦。

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define inf (0x3f3f3f3f)
typedef long long int LL;
typedef unsigned long long int ULL;
#include <iostream>
#include <sstream>
#include <vector>
#include <set>
#include <map>
#include <queue>
#include <string>
//BKDRHash,最优的字符串hash算法。hash一开始是等于0的
//for(i=1 to lenstr) hash = seed * hash + str[i]; 这是求解hash值的公式
//我们希望,对于任意给定的区间[L,R],都能快速地算出它的hash值是多少
//明显我们要算的是:例如[3,4] ans = seed * str[3] + str[4]
//那么我们先预处理一个前缀hash总和,记为sumHash[i]表示1~i的hash值
//sumHash[1] = str[1]; sunHash[2] = seed * str[1] + str[2];
//sunHash[3] = seed * sumHash[2] + str[3]; sumHash[4] = seed * sumHash[3] + str[4];
//拆出来,得到若要求[3,4] 既可以 ans = sumHash[4] - seed^(R-L+1) * sumHash[2]
//所以预处理两个数组,powseed[i]表示seed的i次方, sumHash[i]定义如上
const int seed = ; // 31 131 1313 13131 131313 etc..
const int maxn = +;
char str[maxn];
ULL powseed[maxn]; // seed的i次方 爆了也没所谓,sumHash的也爆。用了ULL,爆了也没所谓,也能唯一确定它
ULL sumHash[maxn]; //前缀hash值
int ans[maxn][maxn]; //ans[L][R]就代表ans,就是区间[L,R]内不同子串的个数
const int MOD = ;
struct StringHash
{
//int book[MOD+20];
map<ULL,int>book;
void init ()
{
book.clear(); return ;
}
int insert (ULL val,int id)
{
if (book[val])
{
int t = book[val];
book[val] = id;
return t;
}
book[val] = id;
return ;
}
}H;
void work ()
{
scanf ("%s",str+);
int lenstr = strlen(str+);
for (int i=;i<=lenstr;++i)
sumHash[i] = sumHash[i-]*seed + str[i];
memset(ans,,sizeof(ans));
for (int L=;L<=lenstr;++L) //暴力枚举子串长度
{
H.init();
for (int i=;i+L-<=lenstr;++i)
{
int pos = H.insert(sumHash[i+L-]-powseed[L]*sumHash[i-],i);
ans[i][i+L-] ++;//ans[L][R]++,自己是一个
ans[pos][i+L-]--;//pos放回0是没用的
//就像bababa,第二个ba的时候,会ans[1][4]--;表明[1,4]重复了一个
//然后第三个ba的时候,ans[2][6]--,同理,表明[2,6]也是重复了
//那么ans[1][6]重复了两个怎么算?就是在递推的时候,将ans[2][6]的值覆盖上来的
//ans[1][6] += ans[2][6] + ans[1][5] - ans[2][5];
}
}
for (int i = lenstr; i>=; i--)
{
for (int j=i;j<=lenstr;j++)
{
ans[i][j] += ans[i+][j]+ans[i][j-]-ans[i+][j-];
}
}
int m;
scanf ("%d",&m);
while (m--)
{
int L,R;
scanf ("%d%d",&L,&R);
printf ("%d\n",ans[L][R]);
}
return ;
} int main ()
{
#ifdef local
freopen("data.txt","r",stdin);
#endif
powseed[] = ;
for (int i = ; i <= maxn-; ++i) powseed[i] = powseed[i-] * seed;
int t;
scanf ("%d",&t);
while (t--) work();
return ;
}

HDU 4622 Reincarnation Hash解法详解的更多相关文章

  1. hdu 4622 Reincarnation(后缀数组)

    hdu 4622 Reincarnation 题意:还是比较容易理解,给出一个字符串,最长2000,q个询问,每次询问[l,r]区间内有多少个不同的字串. (为了与论文解释统一,这里解题思路里sa数组 ...

  2. Python操作redis系列以 哈希(Hash)命令详解(四)

    # -*- coding: utf-8 -*- import redis #这个redis不能用,请根据自己的需要修改 r =redis.Redis(host=") 1. Hset 命令用于 ...

  3. LeetCode(42.接雨水)多解法详解

    接雨水解法详解: 题目: 基本思路:从图上可以看出要想接住雨水,必须是凹字形的,也就是当前位置的左右两边必须存在高度大于它的地方,所以我们要想知道当前位置最多能存储多少水,只需找到左边最高处max_l ...

  4. hdu 4622 Reincarnation 字符串hash 模板题

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4622 题意:给定一个长度不超过2000的字符串,之后有不超过1e5次的区间查询,输出每次查询区间中不同 ...

  5. HDU 4622 Reincarnation 后缀自动机 // BKDRHash(最优hash)

    Reincarnation Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 131072/65536 K (Java/Others) P ...

  6. HDU 4622 Reincarnation (查询一段字符串的不同子串个数,后缀自动机)

    Reincarnation Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 131072/65536 K (Java/Others)To ...

  7. hdu 4622 Reincarnation

    http://acm.hdu.edu.cn/showproblem.php?pid=4622 用字典树把每一个字符串对应成一个整数 相同的字符串对应到相同的整数上 把所用的串对应的整数放在一个数组里 ...

  8. hdu 4622 Reincarnation trie树+树状数组/dp

    题意:给你一个字符串和m个询问,问你l,r这个区间内出现过多少字串. 连接:http://acm.hdu.edu.cn/showproblem.php?pid=4622 网上也有用后缀数组搞得. 思路 ...

  9. hdu 4622 Reincarnation SAM模板题

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4622 题意:给定一个长度不超过2000的字符串,之后有Q次区间查询(Q <= 10000),问区 ...

随机推荐

  1. BZOJ1972:[SDOI2010]猪国杀

    我对模拟的理解:https://www.cnblogs.com/AKMer/p/9064018.html 题目传送门:https://www.lydsy.com/JudgeOnline/problem ...

  2. Linux环境下,开启tomcat时报transport error 202: bind failed: 地址已在使用

    转载自:http://blog.csdn.net/mooncom/article/details/61913813 问题描述:今天我在Linux环境下配置tomcat,在tomcat/conf下的se ...

  3. 查看linux上所有用户

    1.查看所有用户名 cat /etc/passwd |cut -f 1 -d #是1不是L的小写 2.显示用户信息 whoami 查看当前登录用户名. id username 查看用户的uid,gid ...

  4. 【转】 Pro Android学习笔记(七五):HTTP服务(9):DownloadManager

    目录(?)[-] 小例子 保存在哪里下载文件信息设置和读取 查看下载状态和取消下载 文章转载只能用于非商业性质,且不能带有虚拟货币.积分.注册等附加条件,转载须注明出处:http://blog.csd ...

  5. [.net] 无法创建虚拟目录。已将URL“XXX”映射到IIS Express网站上的一个不同的文件夹

    工作时,在修改项目属性,Web中服务器时,出现了下面的错误: 各种折腾后,找到下面的解决方法: 1.找到项目在本地的目录,目录下有当前项目的项目文件,文件名以.csproj为后缀名. 2.用文本编辑软 ...

  6. 用python做的windows和linx文件夹同步。解决自动同步、加快传输大量小文件的速度、更丰富的文件上传过滤设置。

    现在工具不好用,用的pycharm自动同步,但对于git拉下来的新文件不能自动上传到linux,只有自己编辑过或者手动ctrl + s的文件才会自动同步.导致为了不遗漏文件,经常需要全量上传,速度非常 ...

  7. Java探索之旅(6)——对象和类

    1.知识要点 假设: public ClassName{     int data;   String name;     ClassName(){data=1;}     public static ...

  8. Java静态检测工具/Java代码规范和质量检查简单介绍(转)

    静态检查: 静态测试包括代码检查.静态结构分析.代码质量度量等.它可以由人工进行,充分发挥人的逻辑思维优势,也可以借助软件工具自动进行.代码检查代码检查包括代码走查.桌面检查.代码审查等,主要检查代码 ...

  9. 7.29实习培训日志-Oracle题目

    总结 这周主要学习了SQL,oracle中的SQL基础,以前学习的是SQLserver的SQL,和oracle的还是有略微不同,所以重新去学习了一段时间,然后对于oracle中的各种函数有了初步了解, ...

  10. Vue.js 源码实现

    目录 Vue.js 代码实现 1. 步骤一 2. 步骤二 3.步骤三 Vue.js 工作机制 初始化 编译 响应式 虚拟dom 更新视图 编译 Vue.js 代码实现 检验学习效果的最好方法就是自己造 ...