题目描述

这天天气不错,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]的长度为$2^{i-1}$
$hash(A[i])=hash(A[i-1]) * base^{2^{i-2}}+hash(not(A[i-1]))$
现在假设$f[i]=hash(A[i])-hash(not(A[i]))$
则:$f[i]=f[i-1] * (base^{2^{i-2}}-1)$
假设$g[i]=base^{2^{i-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都是偶数。因此:
$$2^{i-1}|f[i]$$
不过这样还不够,因为我们不能构造$2^{64}$这么长的字符串,所以继续分析:
$g[i]=base^{2^{i-1}}-1=(base^{2^{i-2}}-1)(base^{2^{i-2}}+1)$,所以$g[i]=g[i-1]*$偶数
所以$2^i|g[i]$,所以
$$2^{i * \frac{i-1}{2}}|f[i]$$
i=11的时候就可以卡掉了!所以构造就很简单了。

代码