最小表示法,字符串哈希

最小表示法,字符串哈希


最小表示法

给出字符串 \(S\) ,在其所有的循环同构串中选择字典序最小的一个串

循环同构:当字符串 \(S\) 中可以选择一个位置 \(i\) ,满足 \(S_{i,n}+S_{1,i-1}=T\) 那么记 \(S,T\) 循环同构

可以利用类似双指针的办法解决

  • 构造字符串 \(\hat{S}=S+S\) ,设立两个指针 \(u,v\)

例如: \(S=b\ c\ a\ d\ a\ b\ d\ d\)\(\hat{S}=b\ c\ a\ d\ a\ b\ d\ d\ b\ c\ a\ d\ a\ b\ d\ d\)

一开始 \(u=1,v=2\) 设前 \(k\) 个字符相同,当 \(\hat{S}_{u+k}\ne \hat{S}_{v+k}\) 时,指针可以移动

  • \(\hat{S}_{u+k}> \hat{S}_{v+k}\) 说明以 \(u\) 为开始的循环同构串字典序大于以 \(v\) 开始的,那么移动 \(u=u+k+1\)
  • \(\hat{S}_{u+k}< \hat{S}_{v+k}\) 说明以 \(v\) 为开始的循环同构串字典序大于以 \(u\) 开始的,那么移动 \(v=v+k+1\)

因为 \(\hat{S}_U\)\(\hat{S}_V\)\(k\) 个字符相同,说明字典序相同,那么下一次判断可以跳过这段重复的区间

  • 如果 \(\hat{S}_{u+k}= \hat{S}_{v+k}\),说明可以增加 \(k\) 区间长度,即 \(k+1\)
string s;
cin>>s;
int n=s.size();
s="0"+s+s;//0占位符,规范下标从1开始

int u=1,v=2,k=0;
while (u<=n&&v<=n){
	for (k=0;k<n&&s[u+k]==s[v+k];k++);
	if (s[u+k]>s[v+k]) u+=k+1;
	else v+=k+1;//相等的情况前面循环已排除
	if (u==v) u++;
}

最后的答案 \(ans={\rm{min}}(u,v)\) ,这是因为在

if (s[u+k]>s[v+k]) u+=k+1;
else v+=k+1;

过程中,如果 \(u,v\) 向后移动超过了 \(n\) 的范围就会立刻终止循环,又因为 \([n+1,2n]\) 的区间与 \([1,n]\) 区间完全一致

那么在 \(n\) 后位置上确定的循环同构串已经在 \(n\) 前判断过,所以输出 \(u,v\) 最小者即可


字符串哈希

给定许多字符串,判断其中是否存在重复的串

如果暴力判断会浪费许多时间在字符串的匹配上,这时候可以利用哈希函数将这个字符串映射为一个数值

映射后不同的数值对应的原字符串一定不相同,相同的数值对应的原字符串不一定相同

显然,我们肯定是理想让不同字符串映射后的数值都不相同(相同称为哈希冲突)

首先是字符串常用的哈希方式:模数哈希

每个字符串的每个字符都对应一个 ASCII 码,天然可以映射为一个数字,那么对于长为 \(n\) 的字符串 \(S\) ,定义它的模数哈希函数为

\[hash=(S_1*B^{n-1}+S_2*B^{n-2}+\cdots+S_{n-1}*B^1+S_{n}*B^0)\ {\rm{mod}}\ M \]

其中的 \(B\) 表示 \(B\) 进制,将整个字符串视为一个 \(B\) 进制的数,整个多项式对 \(M\) 取模

一般的,\(B\) 常取 \(131,13331,13131\)\(M\) 常取 \(2^{32}-1=212370440130137957,10^9+7,19260817\)

const ULL HashBase=13331;
const ULL HashMod=212370440130137957;

ULL hash(string s){
	int len=s.size();
	ULL res=0;
	for (int i=0;i<len;i++) 
		res=(res*HashBase+(ULL)s[i])%HashMod;
	return res;
}

估测哈希冲突的概率函数计算:哈希空间 \(M\) 下插入 \(k\) 个字符串的冲突概率,\(M\) 与模数定义相同

\[p(k,M)=1-e^{-\frac{k(k-1)}{2M}} \]

但是,这样的计算出来的哈希冲突概率仍然较高,可能会被卡

所以可以利用双值哈希的操作,将哈希冲突的概率降低到

\[p(k,M)^2=\left[ 1-e^{-\frac{k(k-1)}{2M}} \right]^2 \]

即采用两个不同的哈希函数映射,当且仅当两个字符串的两个哈希值完全相同时才视作相同

const ULL HashBase=13331;
const ULL HashMod_1=212370440130137957;
const ULL HashMod_2=1e9+7;

ULL hash_1(string s){
	int len=s.size();
	ULL res=0;
	for (int i=0;i<len;i++) 
		res=(res*HashBase+(ULL)s[i])%HashMod_1;
	return res;
}

ULL hash_2(string s){
	int len=s.size();
	ULL res=0;
	for (int i=0;i<len;i++) 
		res=(res*HashBase+(ULL)s[i])%HashMod_2;
	return res;
}
posted @ 2025-03-09 13:07  才瓯  阅读(53)  评论(0)    收藏  举报