字符串哈希 详解+例题

字符串哈希

观看讲解视频:董晓算法 做的笔记

理论部分

字符串哈希 是把不同的字符串映射成不同的整数。

  • 对于一个长度为 \(n\) 的字符串 \(s\),我们定义它的 Hash 函数为:
    \(h(s) = \Sigma ^n_{i=1}\) \(s[i] \times p^{n-i}\) \((mod\) \(m)\)
    例如:字符串 \(abc\),他的 hash 函数值为 \(ap^2+bp^1+cp^0\)
    \(97\times131^2+98\times131^1+99\)

  • 如果两个字符串不一样,Hash 值却一样,这样的现象称为 哈希碰撞

  • 解决哈希碰撞的方法:
    保证 \(p\)\(m\) 互质,\(p\) 通常取质数 \(131\)\(13331\)\(m\) 通常取大整数 \(2^64\),哈希函数要定义为 ULL(unsigned long long),超过则自动溢出,等价于取模。

关于 unsigned long long:无符号,取值范围在 \(0 \sim 2^{64}\)

代码实现:

  1. 求一个字符串的哈希值:前缀和
    前缀和:\(h[i] = h[i-1] \times p + s[i]\)\(h[0] = 0\) (\(h[i]\) 表示前 \(i\) 项的 hash 值)
    时间复杂度:\(O(n)\)
  2. 他的子串的哈希值就是区间和
    区间和:\(h[l,r] = h[r] - h[l] \times p^{r-l+1}\)
    时间复杂度:\(O(1)\)

例题:

P3370 【模板】字符串哈希

模板题,直接上代码。

#include<bits/stdc++.h>

using namespace std;

#define ULL unsigned long long

const int maxn=100010;
const int p=131;

int n;
int lens;
char s[maxn];
ULL ans[maxn],h[maxn];
int cnt=0;

ULL calc(char s[],int n)
{
	h[0]=0;
	for(int i=1;i<=lens;i++)
	{
		h[i]=h[i-1]*p+s[i]; //求 hash 值,前缀和,便于求区间 hash 值(虽然这题不需要 QWQ ) 
	}
	return h[lens];
 } 
 //计算哈希值 

int main(){
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s+1);
		lens=strlen(s+1);
		ans[i]=calc(s,lens);//计算哈希值
	}
	sort(ans+1,ans+1+n);
	for(int i=1;i<=n;i++)
	{
		if(ans[i]!=ans[i-1])cnt++;
	 } 
	cout<<cnt<<endl;
	return 0;
} 

魔族密码

题目意思:

求最长词链。(其实就是关于字符串的最长上升子序列了)
输入保证了字符串按字典序排列且没有重复的(太良心了)

词链:多个词组成的表中,前一个单词是后一个单词的前缀。

思路:

不是吧??我这个蒟蒻都想起来了两种方法。。(虽然只会实现一种)

  • 一看前缀,就是字典树
    让节点的 cnt 值表示有多少个 s 在这里结尾
    每次插入一个字符串,查找这个字符串经过节点的 cnt 值之和
    跟原答案求一个 max 值
    代码:
#include<bits/stdc++.h>

using namespace std;

int n;
int ans=0;
int cnt=1;

struct node{
	int son[65];
	int cnt;
}z[3000100];

int getnumber(char ch)
{
	return ch-'a'+1;
}

void insert(string s)
{
	int now=1;
	int cnt_s=1;
	for(int i=0;i<s.length();i++)
	{
		int num=getnumber(s[i]);
		if(!z[now].son[num])
			cnt++,z[now].son[num]=cnt;
		cnt_s+=z[now].cnt;
		now=z[now].son[num];
	}
	z[now].cnt++;
	ans=max(ans,cnt_s);
	return;
}

int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		string s;
		cin>>s;
		insert(s);
	}
	cout<<ans<<endl;
	return 0;
}
  • 但是标签里哈希
    考虑哈希做法,此处参考 https://www.luogu.com.cn/article/bu7eorhv
    因为输入保证按照字典序排列,因此如果 \(s_j\)\(s_i\) 的前缀,则必然会有 \(j < i\)
    这样在计算 \(h(s_i)\) 的值的时候,计算每个前缀和的时候,查找一下前面是否有这一个值就行了。
    我们可以推出状态转移方程:\(dp_i = max\) \(\{{dp_i},{dp_j+ 1}\}\)
    代码:我说没有行吗。。。(逃
    其实是我不会

完结撒花!

posted @ 2024-08-27 19:49  lazy_ZJY  阅读(7)  评论(0编辑  收藏  举报