Loading

Hash学习笔记

由老师的课件整理而成


目录

  1. 前言
  2. 整数hash
    2.1 hash冲突
    2.2 如果 \(7\) 和 \(19260824\) 都出现了
  3. 字符串hash
    3.1 与顺序有关的hash
    3.2 与顺序无关的hash
    3.3 与顺序和字母都无关的hash
  4. 例题
  5. 推荐习题

1.前言

今天学 \(hash\) 直接学蒙,
于是写篇博客来整理一下思绪。

2. 整数hash

作用: 在 \(O(1)\) 时间内判断整数是否出现。
设 \(h(x)\) \(=\) \(x\) % \(P\) ,保证 \(p\) 是个质数且 \(p\) 不是很大。
设 \(F\) 数组
\(F[ h(x) ]\) 来判断 x 是否出现。

2.1 hash冲突

有时候,我们会遇到hash冲突的情况,也就是 \(x\) \(!= y\) 但是 \(h(x) = h(y)\)
如:想判断 \(7\) 是否出现,但是 \(7\) 没有出现,\(19260824\) 出现了,所以 \(F[h(7)]=1\),但实际上 \(7\) 没有出现。

如何解决?
设一 \(G\) 数组。
当 \(x\) 插入的时候,将 \(F[h(x)]\) 置为 \(1\), 同时,我们将 \(G[h(x)]\) 置为 \(x\) 就可以了。

2.2 如果 7 和 19260824 都出现了

第一种方法:
对于 \(x\),希望将其加入到 \(h(x)\) 位置,但是可能 \(h(x)\) 已经被占据,这个时候,
我们构造一个数列 \(S\) ,依次检查 \(S\) 的每一个位置,如果位置上没有被占据,就占据这个位置。
那么 $S = $ { $ h(x) , h(x)+1, h(x)+2, h(x)+3, .. $}
所以 \(S_ 7 =\) { \(h(7), h(7)+1, h(7)+2, h(7)+3, ..\) }
假设 \(19260824\) 已经加入了,也就是说 \(h(7)\) 已经被占据,
那么检查 \(h(7)+1\) 是否已经被占据,如果是,则检查下一个,直到找到一个 \(h(7)+u\) 没有被占据。
找到后,就使 \(F[h(7)+u] = 1\), \(G[h(7)+u] = 7\) 。

代码实现:

void hash(int x) 
{
  pos=x%p;
  while(F[pos]) pos++;
  F[pos]=1,G[pos]=x;
}
int judge(int x) 
{
  pos=x%p;
  while(F[pos]&&G[pos]!=x) ++pos;
  return F[pos];
}

码风巨丑,勿喷

如果插入二元组 \((x, y)\),
已知 \(F\) 有方程 \(F(x) = F(\sqrt{x})\) \(+F(\sqrt{x}+1)\)。
我们转移需要利用到 \(F\) 的值,但是这个值是离散的,比如我要算 \(F(10^ {12})\),实际上需要用的
\(F\) 值只有 \(10^ 5\),这个时候就可以考虑使用 hash 来存下 \(F\) 的值。
相当于二元组 \((x, F(x))\)。

代码实现:

void hash(x,y) 
{
  pos=x%p;
  while(F[pos]) pos++;
  F[pos]=x,G[pos]=y;
}
int judge(int x) 
{
  pos=x%p;
  while(F[pos]&&F[pos]!=x) ++pos;
  return G[pos];
}
int f(int x) 
{
  int t=judge(x);
  if(t) return t;
  fx = ...//省略
  hash(x,fx)
  return fx
}

第二种方法:
内置链表,在每一个节点内置一个链表 \((vector)\)。
会快一些,但是难写一些。

代码实现:

void hash(int x,int y) 
{
  pos=x%p;
  v[pos].push_back((x, y));
}
int judge(int x,int y) 
{
  pos=x%p;
  for(int i=0;i<v[pos].size();i++)
    if(v[pos][i].first==x)
      return v[pos][i].second;
  return -1;
}

链表开的空间一般是 \(100\) 倍,如果存 \(10^ 5\) 个数,则数组开到 \(10^ 7\), 否则没有办法保证时间复杂度了。

3. 字符串 hash

3.1 与顺序有关的 hash

需要判断字符 \(S\) 是否出现过(字符必须完全一样)。

第一种做法
可得: \(h(S) = (S[0] + S[1]\) \(×\) \(p + S[2]\) \(×\) \(p^ 2 + S[3] × p^ 3 + ... )\) % \(p_ 2\)
所以判断 \(S\) 是否出现即为判断 \(h(S)\) 是否被占据。
一般数组开到 $min(10^ 7, $ 数据量\(^ 2)\) 。
此做法一般赌脸,脸白便能过。

第二种做法 \((double\) \(hash)\)
几乎不可能被卡,除非你的 \(p_ 2, q_ 2\) 被出题人知道了,或者 \(p_ 2\) 或者 \(q_ 2\) 等于 \(19260817\)。
\(h1(S) = (S[0] + S[1] × p + S[2] × p^ 2 + S[3] × p^ 3 + .. )\) % \(p2\)
\(h2(S) = (S[0] + S[1] × q + S[2] × q^ 2 + S[3] × q^ 3 + .. )\) % \(q2\)
则判断 \(S\) 是否出现即为判断二元组 \((h1(S), h2(S))\) 是否出现。

3.2 与顺序无关的 hash

定义字符串 \(S\) , \(T\) 相似,
相似指当且仅当 \(S\) , \(T\) 内每个出现的字符的出现次数相同。
则 \(S\) 和 \(T\) 相似即为 \(h(S) = h(T)\)。

3.3 与顺序和字母都无关的 hash

定义字符串 \(S\) , \(T\) 相似当且仅当 \(S\), \(T\) 内字符的出现次数集合相同。
例如 \(abab\) 和 \(yyxx\) 相似,因此出现次数集合都为 { \(2,2\) }

4.例题

P3823 [NOI2017] 蚯蚓排队
我们可以在 \(hash\) 表内存 \(F\) , \(G\) ,\(F\) 意义同上,\(G\) 表示出现次数,刚插入时 \(G=1\),之后每次插入的时候,
做一遍查找,如果存在,\(G+1\) ,否则继续插入。

操作1:
因为每次合并最多产生 \(k-1\) 个向后 \(k\) 数字串,
于是我们可以暴力合并,把新产生的向后 \(k\) 数字串存到 \(hash\) 表内。
时间复杂度 : \(O(nk)\)

操作2:
对于分离操作,最多减少 \(k-1\) 个向后 \(k\) 数字串。
我们暴力删除,把减少的向后 \(k\) 数字串在 \(hash\) 中删除(实际上就是让 G-1)。
时间复杂度: \(O(Ck)\)

操作3:
直接在 \(hash\) 表内查询。

优化:
首先把所有的操作都读进来,然后把所有操作需要用到的向后 \(k\) 数字串都
在 \(hash\) 表内保存一个位置,则 \(1, 2\) 操作如果插入或者删除的串 \(hash\) 表内没有,则可以直接跳过。

#include<bits/stdc++.h>
#define ll unsigned long long 
using namespace std;
const int N=200000+5,mod=998244353,Mod=19820826,K=55,NK=N*K*2,bb=13;
ll n,t,tot=0;
ll a[N],st[Mod],then[NK],val[NK],cnt[NK],s[K*4],pre[N],suf[N];
char S[10000005];
void hash(ll x,ll y)
{
	for(ll i=st[x%Mod];i;i=then[i])
		if(val[i]==x)
		{
			cnt[i]+=y;
			return ;
		}
	then[++tot]=st[x%Mod];
	st[x%Mod]=tot;
	val[tot]=x;
	cnt[tot]=y;
	return ;
}
ll qq(ll x)
{
	ll res=0;
	for(ll i=st[x%Mod];i;i=then[i])
		if(val[i]==x)
			return cnt[i];
	return 0;
}
void cz(ll x,ll y,ll val)
{
	ll l=50,r=51;
	for(ll i=x;i&&l>=1;i=pre[i],l--)
		s[l]=a[i];
	for(ll i=y;i&&r<=100;i=suf[i],r++)
		s[r]=a[i];
	l++;
	r--;
	for(ll i=l;i<=50;i++)
	{
		ll ab=0;
		for(ll j=i;j<=50;j++)
			ab=ab*bb+s[j];
		for(ll j=51;j<=r&&j-i+1<=50;j++)
		{
			ab=ab*bb+s[j];
			hash(ab,val);
		}
	}
	return ;
}
int main()
{
	ios::sync_with_stdio(false);
	cin>>n>>t;
	for(ll i=1;i<=n;i++)
	{
		cin>>a[i];
		hash(a[i],1);
	}
	while(t--)
	{
		ll op,x,y;
		cin>>op;
		if(op==1) 
		{
			cin>>x>>y;
			cz(x,y,1);
			pre[y]=x;
			suf[x]=y;
		}
		if(op==2) 
		{
			cin>>x;
			cz(x,suf[x],-1);
			pre[suf[x]]=0;
			suf[x]=0;
		}
		if(op==3) 
		{
			ll x;
			cin>>S>>x;
			y=strlen(S);
			ll ans,ab=0,abab=1;
			for(ll i=0;i<x;i++)
			{
				ab=ab*bb+(S[i]-48);
				abab*=bb;
			}
			ans=qq(ab);
			for(ll i=x;i<y;i++)
			{
				ab=ab*bb-(S[i-x]-48)*abab+(S[i]-48);
				ans=1ll*ans*qq(ab)%mod;
				
			}
			cout<<ans<<endl;
		}
	}
	return 0;
}

5.推荐习题

P3370 【模板】字符串哈希
P7469 [NOI Online 2021 提高组] 积木小赛
P3538 [POI2012]OKR-A Horrible Poem

posted @ 2022-01-20 12:44  sheeplittlecloud  阅读(101)  评论(1)    收藏  举报