最小表示法,字符串哈希
最小表示法
给出字符串 $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;
}
评论区(暂无评论)