关于HASH

​ 这应该是经常使用的一个算法,因为其预处理后,优秀的\(O(1)\)处理出子串,并且\(O(1)\)比较,大快人心,而且写法简单,令人心情愉悦;

​ 但是其空间复杂度较高,并且有玄学模数以及哈希冲突,以至于如果想hack,其实可以hack掉;

前置知识

​ 关于进制,模数,hash就用到了重构进制,取模稀疏,所以哈希表又叫稀疏表;

入坑

​ hash很好理解,而且匹配非常方便,不容易写炸,对于萌新十分友好,

HASH查询

​ 我们知道数字匹配复杂度为 \(O(1)\) ,数字匹配速度快,而字符串却只能一个一个匹配, 这不公平 .那么我们考虑将字符串变成数字.

​ 想一下数字有进制,那么我们定义一下字母的进制,不一定是26,我们可以随便取一个数,习惯性取质数;

​ 但是数字太长,爆 \(long\) \(long\) 我们没办法存怎么办,我们考虑字符串很少,但是空间很大,我们考虑将数字安排入一个位置,其实这个位置是随机的,但是我们可以推出,这就够了;

​ 那么我们可以模一个数字,将数字限制在一个范围之内,然后储存下来,而这个模数一般是一个质数,因为质数的特殊性质,可以造成更好地将数字稀疏;

模版

#define ll long long
const ll base=133;
const ll mod=19491001;
ll id(char s[]){
int l=strlen(s);ll ol=0;
for(int i=0;i<l;i++)
ol=(ol*base+s[i]-'a'+1)%mod;
return ol;
}

​ 处理复杂度 \(O(n)\) 然后开个数组储存即可;

HASH子串匹配

​ 我们知道,如果每次都处理出一个串的子串,那么时间复杂度 \(O(n^2)\) ,这是我们不能接受的,但是考虑一下我们存的是数字,数字有进制,那么一定可以通过加减操作得到其子串,那么,就简单很多了;

​ 我们可以先预处理出 \(HASH\) 前缀和,之后通过加减得到一段区间的子串;

​ 但是直接开数组了话,有可能开不下,或者加大hash冲突的可能(即两个字符串hash值相同),那么我们可以考虑在不超时的情况下,加入一个map储存hash值,这样不需再担心空间问题,但是每次查询时间会多一个 \(logn\) ,请谨慎使用;

例题

​ 我不再直接附上模版,而是引入一个 例题;

给出几个由小写字母构成的单词,求它们最长的公共子串的长度。

做法

​ 我们可以二分枚举公共子串长度,因为公共子串长度一定是满足单调性质的.

​ 那么我们选择枚举第一个串的子串,然后将其他串中相同长度的子串储存起来,那么就可以匹配了;

code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#define maxn 2007
#define ll long long
using namespace std;
const ll base=133;
const ll mod=998244353;
map<int,bool>ha[6];
ll sum[6][maxn],ad[maxn];
int n,lim=maxn,len[6];
char c[6][maxn]; template<typename type_of_scan>
inline void scan(type_of_scan &x){
type_of_scan f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
x*=f;
}
template<typename top,typename... tops>
inline void scan(top &x,tops&... X){
scan(x),scan(X...);
} int id(int pos,int l,int r){
return (sum[pos][r+1]-sum[pos][l]*ad[r-l+1]%mod+mod)%mod;
} bool check(int l){
for(int i=1;i<=n;i++) ha[i].clear();
for(int i=2;i<=n;i++){
for(int j=0;j+l-1<len[i];j++)
ha[i][id(i,j,l+j-1)]=1;
}
for(int i=0;i+l-1<len[1];i++){
int temp=id(1,i,i+l-1),cnt=n-1;
for(int j=2;j<=n;j++){
if(ha[j][temp]) cnt--;
}
if(!cnt) return 1;
}
return 0;
} void init(){
ad[0]=1;
for(int i=1;i<=2001;i++) ad[i]=ad[i-1]*base%mod;
for(int i=1;i<=n;i++){
for(int j=0;j<len[i];j++){
sum[i][j+1]=(sum[i][j]*base+(c[i][j]-'a'))%mod;
}
}
} int main(){
scan(n);
for(int i=1;i<=n;i++)
scanf("%s",c[i]),lim=min(len[i]=strlen(c[i]),lim);
int l=0,r=lim;init();
while(l<r){
int mid=l+r+1>>1;
if(check(mid)) l=mid;
else r=mid-1;
}
printf("%d\n",l);
}
TO BE CONTINUED

最新文章

  1. 调用webservice进行身份验证
  2. 很强大的HTML+CSS+JS面试题(附带答案)
  3. 改造rm命令为mv
  4. ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(二) 之 ChatServer搭建,连接服务器,以及注意事项。
  5. ios-系统警告框 跳转到设置里面
  6. linux-4 虚拟机安装VMwareTOOls工具包
  7. chenxi的html学习笔记
  8. 钣金的折弯成型工艺(Press Braking)
  9. sqlserver2005唯一性约束
  10. 用MySQL创建数据库和数据库表
  11. 【git 问题小说说】 git add时候报错:LF will be replaced by CRLF
  12. float编码杂谈
  13. 讲讲金融业务(一)--自助结算终端POS
  14. 7.数据本地化CCString,CCArray,CCDictionary,tinyxml2,写入UserDefault.xml文件,操作xml,解析xml
  15. Visual C++ 6.0中if..else..的简单用法和基本格式
  16. laravel项目thinksns+安装pc前端页面
  17. lamp 5.6.36 bug记录
  18. Centos下查看和修改网卡Mac地址
  19. PTA(BasicLevel)-1008数组元素循环右移问题
  20. 编写高质量代码改善C#程序的157个建议——建议103:区分组合和继承的应用场合

热门文章

  1. 十六:SQL注入之查询方式及报错盲注
  2. Java并发/多线程-CAS原理分析
  3. 【Linux】ntp的一些坑。你肯定遇到过
  4. 【ORA】ORA-01033,ORA-09968,ORA-01102
  5. leetcode 470. 用 Rand7() 实现 Rand10() (数学,优化策略)
  6. bash5.0参考手册
  7. List使用Stream流进行集合Collection的各种运算汇总:对BigDecimal求和,某个字段的和、最大值、最小值、平均值,字段去重,过滤等
  8. linux搭建简单samba服务器
  9. day128:MySQL进阶:
  10. MySQL增删改操作