笔记 哈希

A - Description

给定字符串 \(S\)\(T\),问你在 \(S\)\(T\) 出现了几次。

\(1\le |S|,|T|\le 2\times 10^6\)

A - Solution

首先暴力地来想。在 \(S\) 中找出所有长度为 \(|T|\) 的子串,然后暴力逐字符匹配,复杂度显然是 \(O(n^2)\) 的,考虑优化。(此处假设 \(|T|\)\(|S|\) 同阶,记为 \(n\)

枚举所有子串的过程较难优化,我们考虑优化逐字符匹配的过程。逐字符匹配本质上就是比较两个字符串是否相等,那我们能否将这个过程优化成 \(O(1)\) 呢?

于是,字符串哈希应运而生。我们可以使用字符串哈希将字符串压成一个数,然后就可以 \(O(1)\) 比较了。考虑将这个字符串转化为一个 \(k\) 进制数,也就是给第 \(i\) 位乘上 \(k^{i-1}\) 的权值。防止大数溢出,可以设定模数进行取模(不过大多数时候 unsigned long long 是不错的选择)

时间复杂度 \(O(n)\),瓶颈在于枚举长度为 \(|T|\) 的子串。

B - Description

给定字符串 \(S\),有 \(q\) 次询问,每次询问给出 \(x\)\(y\),让你求字符串 \(s_1=S_xS_{x+1}\cdots S_{n}\)\(s_2=S_yS_{y+1}\cdots S_n\) 的最长公共前缀长度。

B - Solution

还是暴力地来想。假设答案为 \(len\),那么就有 \(S_xS_{x+1}\cdots S_{x+len-1}=S_yS_{y+1}S_{y+len-1}\) ,有了前面的哈希支持,我们可以做到 \(O(1)\) 判断这个式子。我们现在可以枚举从 \(1\)\(n-\max(x,y)+1\) 的每个 \(len\)(记 \(n=|S|\),下文如没有特殊说明均代表此含义),时间复杂度是 \(O(qn)\) 的。

进行一些观察,我们发现 \(len\) 是具有单调性的。具体来说,如果 \(len=5\) 时满足条件,那么 \(n=4\) 时一定满足,因为这是被 \(len=5\) 完全包含的。如果令 \(a_i\) 代表 \(len=i\) 时是否符合条件,那么 \(01\) 数组 \(a\) 的形式一定是 \(1111\cdots 10000\cdots 0\)

因此,我们考虑二分 \(len\)。具体地,每二分出一个 \(mid\),我们使用哈希 \(O(1)\) 判断是否符合条件,如果符合就去二分 \(>mid\) 的值,如果不符合就二分 \(<mid\) 的值。时间复杂度为 \(O(q\log n)\),可以通过。

C - Description

给你一个由小写英文字母构成的字符串 \(S\),求 \(S\) 中最长回文串的长度。

\(1\le n\le 10^6\)

C - Solution

我们先从回文串的定义角度入手。通俗一点来说,回文串就是指从前往后读和从后往前读是相同的。形式化地,对于一个字符串 \(s\),有 \(s_1s_2\cdots s_n=s_ns_{n-1}\cdots s_1\)。不过貌似和最长没有什么关系,所以换一种方法表示。\(s_1s_2\cdots s_{mid}=s_{n}s_{n-1}\cdots s_{mid}\)。现在我们把答案扩展到了一个 \(mid\) 上。\(mid\) 是可以从 \(1\)\(n\) 枚举的,所以现在只需要考虑 \(mid\) 为定值时的情况。

对于这种极值问题,我们可以将它转化为判定性问题。具体地,对于每一个 \(1\le mid \le n\),我们需要求出最大的 \(len\),使得

\[s_{mid-len+1}s_{mid-len+2}\cdots s_{mid}=s_{mid+len-1}s_{mid+len-2}\cdots s_{mid} \]

根据上一道题的观察,\(len\) 也是具有单调性,可以二分的。而上面的式子也是可以哈希 \(O(1)\) 判断的。因此,我们解决了这个问题。

流程如下:

  • 枚举每个 \(1\le mid \le n\),需要 \(O(n)\)
  • 二分出最大的 \(len\),满足上面的等式(哈希判断),需要 \(O(\log n)\)

哦哦其实并没有解决这个问题。因为我们还不知道如何求解一个区间内字符的哈希值。

我们先列出来最基本的求区间 \(\left [ 1,i\right ]\) 哈希的式子:

\[hash\left [ i\right ]=\sum_{j=1}^i s_j\times base^{i-j} \]

那么如何用已知的 \(hash\) 数组表示一个区间 \(\left [ l,r\right ]\) 的哈希值?

\[\begin{align} hash(l,r)&=\sum_{j=l}^r s_j\times base^{r-j}\\ &=\sum_{j=1}^rs_j\times base^{r-j}-\sum_{j=1}^{l-1}s_j\times base^{r-j}\\ &=hash\left [r\right ]-hash\left [ l-1 \right ]\times base^{r-j-(l-1-j)}\\ &=hash \left [r\right ]-hash\left [l-1\right ]\times base^{r-l+1} \end{align} \]

写点解析吧。

第一行:将 \(l\)\(r\) 这个子串表示为 \(base\) 进制数。可以理解为把 \(l\)\(r\) 单独看成一个字符串重新哈希得到的值。

第二行:类似前缀和的操作,显然正确。

第三行:注意到这个式子中减号前后的形式基本与最开始的哈希式子一样,于是改写成 \(hash\) 数组。后者在区间哈希的式子中 \(base\) 的幂次是 \(r-j\),而写成 \(hash[l-1]\) 的话幂次就应该是 \(l-1-j\)

第四行是简单化简,于是我们得到了 \(O(1)\) 区间哈希的方法。

综上,时间复杂度为 \(O(n\log n)\)

D - Description

给你一个长度为 \(n\) 的序列 \(a\),保证对于每个 \(1\le i\le n\)\(1\le a_i\le n\),有 \(q\) 次询问,每次询问给定两个正整数 \(l,r\),你需要回答区间 \(\left [l,r\right ]\) 是否是 \(\left [1,r-l+1\right ]\) 的排列。

\(1\le n,q\le 10^6\)

D - Solution

首先,我们发现如果 \(\left [ l,r \right ]\)\(\left [ 1,r-l+1 \right ]\) 的排列,那么这两个区间和一定相等。因此, \(\left [l,r \right ]\) 的区间和一定等于 \(\frac{x\times (x+1)}{2}\)\(x=r-l+1\),即为区间长度)。只有这一个条件显然是不严谨的,我们再将排列的基本性质放到限制里:区间中的 \(x\) 个元素各不相同。

形式化地,满足下面两个条件的区间是符合条件的。

  • \(\forall \ l\le i,j\le r\),如果 $i\ne j $ 有 \(a_i\ne a_j\)
  • \(sum(l,r)=\frac{x\times (x+1)}{2}\)

第二个条件显然是可以 \(O(1)\) 判断的,所以我们只需考虑第一个条件。

如何用更方便观察的方式记录区间内是否有相同元素?我们考虑记 \(pre_i=j\) 来代表 \(i\) 左侧第一个等于 \(a_i\) 的数下标为 \(j\)(如果不存在 \(j\) 则即 \(pre_i\)\(0\))。于是我们有了新的条件。

  • \(\forall\ l\le i\le r\),有 \(pre_i<l\)

\(O(n)\) 预处理 \(pre\) 数组,暴力判断每个 \(pre_i\),我们成功做到了 \(O(nq)\) 的复杂度。

我们较为巧妙地想到将 \(l\)\(r\) 的区间判定性问题转化到整个序列。显而易见地,对于所有 \(1\le i<l\),都有 \(pre_i<l\),这是一个事实,是百分之百存在的。我们可以从二进制角度看,把这个事件的值记作 \(1\)

而上面 \(l\)\(r\) 的这个条件是有待判断的,不是百分之百存在的。我们记作 \(0\)

注意到 \(1\& 0=0\),也就是说全局来看,我们对于 \(\left [ l,r\right ]\) 的约束和我们对于 \(\left [1,r \right ]\) 的约束是等价的,是不影响结果的。

于是,我们又有了新的条件。

  • \(\forall \ 1\le i\le r\),都有 \(pre_i<l\)

这次就好搞了。我们预处理出 \(pre\),然后做一个类似前缀和的东西维护 \(\left [1,r \right ]\)\(pre\) 的最大值,询问时就可以做到 \(O(1)\) 判断。

最终,我们成功地做到了 \(O(q)\) 的复杂度。

E - Description

给你一个 \(n\)\(m\) 列的 \(01\) 矩阵 \(S\),有 \(q\) 次询问,每次询问给你一个 \(a\)\(b\) 列的 \(01\) 矩阵,问你这个矩阵是否在初始给定的大矩阵中出现过。

\(1\le m,n,q\le 10^3\)\(1\le a,b\le 100\)

E - Solution

还是先从暴力开始看。对于每一次询问,我们可以在 \(S\) 中枚举出所有 \(a\times b\) 的矩阵,然后逐个比较。时间复杂度为 \(O(qnmab)\)。发现这个题目对于每次询问的 \(a,b\) 都是定值,这意味着我们可以预处理。(空间复杂度也是给够 \(n^2\) 的)

如果我们已经求出了 \(S\) 中所有 \(a\times b\) 矩阵的哈希值(记 \(hash_{i,j}\) 为左上角坐标为 \((i,j)\) 的矩阵的哈希值),那只需要求出每次询问中的矩阵哈希值,再在 \(hash\) 数组中查找即可,使用 map 或同类型数据结构可做到单次询问 \(O(ab+\log nm)\approx O(ab)\),总复杂度为 \(O(qab+\ ?\ )\),因为我们不知道怎么预处理。不过貌似只要预处理是与 \(O(nm)\) 同阶的就可以通过此题!我们取得了不小的进步。

首先我们可以将 \(a\times b\) 的矩阵看成长度为 \(a\times b\) 的字符串。这样既直观地将矩阵哈希转变为了字符串哈希,还不影响整体复杂度。

举个例子,下面这个 \(a\times b\) 的矩阵 \(s\) 就可以转化为字符串 \(s^{'}\)

\[S=\begin{bmatrix} s_{1,1} &s_{1,2} &\cdots & s_{1,m}\\ s_{2,1} & s_{2,2} & \cdots & s_{2,m}\\ s_{3,1} & s_{3,2} & \cdots & s_{3,m} \\ \vdots & \vdots & \vdots &\vdots \\ s_{n,1} & s_{n,2} & \cdots & s_{n,m} \end{bmatrix} \\ s\left [ i,j\right ]=\begin{bmatrix} s_{i,j} &s_{i,j+1} &\cdots & s_{i,j+b-1}\\ s_{i+1,j} & s_{i+1,j+1} & \cdots & s_{i+1,j+b-1}\\ s_{i+2,j} & s_{i+2,j+1} & \cdots & s_{i+2,j+b-1} \\ \vdots & \vdots & \vdots &\vdots \\ s_{i+a-1,j} & s_{i+a-1,j+1} & \cdots & s_{i+a-1,j+b-1} \end{bmatrix} \\ s^{'}=s_{i,j}s_{i,j+1}\cdots s_{i+a-1,j+b-1} \\ hash\left [ s^{'}\right ]=\sum_{i=1}^{a\times b} s_{\left \lceil \frac{i}{b}\right \rceil\ ,\ i\ \%\ b}\times base^{a\times b-i} \]

可是我们这样做复杂度并没有优化。

接下来我们使用一种 OI 中常用的优化技巧:考虑偏移量。具体地,我们可以通过右移下移来枚举所有 \(a\times b\) 的矩阵。我们可以思考一种 \(O(1)\) 的转移来优化复杂度。

首先是右移。

\[s=\begin{bmatrix} s_{1,1} &s_{1,2} &\cdots & s_{1,b}\\ s_{2,1} & s_{2,2} & \cdots & s_{2,b}\\ s_{3,1} & s_{3,2} & \cdots & s_{3,b} \\ \vdots & \vdots & \vdots &\vdots \\ s_{a,1} & s_{a,2} & \cdots & s_{a,b} \end{bmatrix} \\ s^{'}=\begin{bmatrix} s_{1,2} &s_{1,3} &\cdots & s_{1,b+1}\\ s_{2,2} & s_{2,3} & \cdots & s_{2,b+1}\\ s_{3,2} & s_{3,3} & \cdots & s_{3,b+1} \\ \vdots & \vdots & \vdots &\vdots \\ s_{a,2} & s_{a,3} & \cdots & s_{a,b+1} \end{bmatrix}\\ hash\left [ s\right ]=\sum_{i=1}^{a\times b} s_{\left \lceil \frac{i}{b}\right \rceil\ ,\ i\ \%\ b}\times base^{a\times b-i} \\ hash\left [ s^{'}\right ]=\sum_{i=1}^{a\times b}s_{\left \lceil\frac{i}{b} \right \rceil\ ,\ i \ \% \ b+1}\times base^{a\times b-i} \]

我们可以类似二维前缀和地处理。然后就支持 \(O(1)\) 了。

F - Description

给定一个字符串 \(S\),有 \(q\) 次询问,每次询问给定 \(1\le l,r\le n\),让你求出字符串 \(S\left [ l,r\right ]\) 的最短循环节。

\(1\le n\le 5\times 10^5\)

\(1\le q\le 2\times 10^6\)

F - Solution

由于最短循环节是一个完整周期,没有字符串叠字符串的情况,所以我们可以观察一些性质。

假设最短循环节的长度为 \(x\),我们把询问时的子串 \(S\left [l,r \right ]\) 分成三部分。

\[S_lS_{l+1}\cdots S_{l+x-1}\ | \ S_{l+x}S_{l+x+1}\cdots S_{r-x}\ | \ S_{r-x+1}S_{r-x+2}\cdots S_r \]

根据题意,字符串 \(S\left [ l,l+x-1\right ]\)\(S\left [ r-x+1,r\right ]\) 就是最短循环节。而中间的部分,则是由若干个最短循环节组成的。显而易见地,当 \(S\left [ l,r-x\right ]=S\left [l+x,r\right ]\) 时,最短循环节的长度 \(x\) 是符合条件的。(这个式子是充要条件)

结合前面区间哈希的式子,我们现在可以 \(O(n)\) 预处理,\(O(1)\) 判断一个给定的长度 \(x\) 是否合法。那么如何求出最小的 \(x\) 呢?我会枚举!从小到大枚举所有的 \(x\),可以做到单次询问最差 \(O(n)\),不过这显然是不够的。

因为区间 \(S\left [l,r\right ]\) 是由若干个长为 \(x\) 的字符串拼成的,所以一个显然的条件是 \(x\ | \ r-l+1\)。我们可以由枚举所有数转化为从小到大枚举 \(r-l+1\) 的因子。线性筛预处理出所有因子,单次询问最差复杂度 \(O(\ln n)\)。由于 \(n\) 只有 5e5,\(O(\ln n)\) 的复杂度是近似线性的。

总复杂度 \(O(q\ln n)\),勉强通过。

posted @ 2025-09-13 23:45  Creativexz  阅读(7)  评论(0)    收藏  举报