题目描述
这天天气不错,hzhwcmhf 神犇给 VFleaKing 出了一道题:
给你一个长度为 N 的字符串 S,求有多少个不同的长度为 L 的子串。
子串的定义是 S[l]、S[l + 1]、… S[r] 这样连续的一段。
两个字符串被认为是不同的当且仅当某个位置上的字符不同。
VFleaKing 一看觉得这不是 Hash 的裸题么!于是果断写了哈希 + 排序。
而 hzhwcmhf 神犇心里自然知道,这题就是后缀数组的 height 中 < L 的个数 + 1,就是后缀自动机上代表的长度区间包含 L 的结点个数,就是后缀树深度为 L 的结点的数量。
但是 hzhwcmhf 神犇看了看 VFleaKing 的做法表示非常汗。于是想卡掉他。
VFleaKing 使用的是字典序哈希,其代码大致如下:
u64 是无符号 int64,范围是 [0, 2^64)。VFleaKing 让 val 自然溢出。
base 是一个常量,VFleaKing 会根据心情决定其值。
VFleaKing 还求出来了 base ^ l,即 base 的 l 次方,这样就能方便地求出所有长度为 L 的子串的哈希值。
然后 VFleaKing 给哈希值排序,去重,求出有多少个不同的哈希值,把这个数作为结果。
其算法的 C++代码如下:
hzhwcmhf 当然知道怎么卡啦!但是他想考考你。
题目分析
随机生成数据直到卡掉此代码(并不)
先%%%% 一下 VFK 神犇:
首先,假如 base 是偶数,则很容易卡掉,因为 aaa…aa 和 baaa…a(长度在 64 以上),即可卡掉('a'=1,'b'=2)
如果 base 是奇数,VFK 神犇首先证明了一个东西:
我们假设 not(A) 表示将 A 字符串 “取反”,即所有 a 都变成 b,所有 b 都变成 a 后得到的字符串。
假设 A[1]="a",A[2]=A[1]+not(A[1])="ab",A[3]=A[2]+not(A[2])="abba"A[4]=“abbabaab" 以此类推。
则 A[i] 的长度为 2i−1
hash(A[i])=hash(A[i−1])∗base2i−2+hash(not(A[i−1]))
现在假设 f[i]=hash(A[i])−hash(not(A[i]))
则:f[i]=f[i−1]∗(base2i−2−1)
假设 g[i]=base2i−1−1
则:f[i]=f[i−1]∗g[i−1], 即 f[i]=f[1]∗g[1]∗g[2]∗g[3]∗…∗g[i−1]
由于 base 是一个奇数,所以 base 的任意次方也是奇数,所以所有的 g 都是偶数。因此:
2i−1|f[i]
不过这样还不够,因为我们不能构造 264这么长的字符串,所以继续分析:
g[i]=base2i−1−1=(base2i−2−1)(base2i−2+1), 所以 g[i]=g[i−1]∗偶数
所以 2i|g[i], 所以
2i∗i−12|f[i]
i=11 的时候就可以卡掉了!所以构造就很简单了。
3 条评论
EdithMadshiZhang · 2020年2月12日 11:04 上午
为何证明 pow(2, i(i-1)/2) | f(i) 后就可以确定 i = 11 就可以卡掉了?
Remmina · 2020年2月14日 8:32 上午
好吧,不得不提醒您这篇文章的作者马上就要高考了,也许我可以帮您召唤一下她……
字符串Hash · 2022年1月24日 11:15 下午
[…] 构造题:BZOJ3097 https://www.mina.moe/archives/2391 […]