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  阅读(60)  评论(1编辑  收藏  举报