题目描述

这天天气不错,hzhwcmhf神犇给VFleaKing出了一道题:
给你一个长度为N的字符串S,求有多少个不同的长度为L的子串。
子串的定义是S[l]、S[l + 1]、… S[r]这样连续的一段。
两个字符串被认为是不同的当且仅当某个位置上的字符不同。

VFleaKing一看觉得这不是Hash的裸题么!于是果断写了哈希 + 排序。
而hzhwcmhf神犇心里自然知道,这题就是后缀数组的height中 < L的个数 + 1,就是后缀自动机上代表的长度区间包含L的结点个数,就是后缀树深度为L的结点的数量。
但是hzhwcmhf神犇看了看VFleaKing的做法表示非常汗。于是想卡掉他。

VFleaKing使用的是字典序哈希,其代码大致如下:

u64 val = 0;
for (int i = 0; i < l; i++)
 val = val * base + s[i] - 'a';

u64是无符号int64,范围是[0, 2^64)。VFleaKing让val自然溢出。
base是一个常量,VFleaKing会根据心情决定其值。
VFleaKing还求出来了base ^ l,即base的l次方,这样就能方便地求出所有长度为L的子串的哈希值。
然后VFleaKing给哈希值排序,去重,求出有多少个不同的哈希值,把这个数作为结果。
其算法的C++代码如下:

typedef unsigned long long u64;

const int MaxN = 100000;

inline int hash_handle(const char *s, const int &n, const int &l, const int &base)
{
 u64 hash_pow_l = 1;
 for (int i = 1; i <= l; i++)
  hash_pow_l *= base;

 int li_n = 0;
 static u64 li[MaxN];

 u64 val = 0;
 for (int i = 0; i < l; i++)
  val = val * base + s[i] - 'a';
 li[li_n++] = val;
 for (int i = l; i < n; i++)
 {
  val = val * base + s[i] - 'a';
  val -= (s[i - l] - 'a') * hash_pow_l;
  li[li_n++] = val;
 }

 sort(li, li + li_n);
 li_n = unique(li, li + li_n) - li;
 return li_n;
}

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的时候就可以卡掉了!所以构造就很简单了。

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<climits>
#include<cstdio>
using namespace std;
const int N=100000;
char s[N+1];int n=1,l;
int main()
{
    s[0]='a';
    for(int i=0;i<12;++i){
        for(int j=0;j<n;++j)
            if(s[j]=='a')s[j+n]='b';
            else s[j+n]='a';
        n<<=1;
    }
    l=n>>1;
    printf("%d %d\n",n+65,l);
    printf("%s",s);
    for(int i=1;i<=65;++i)printf("%c",'a');
    printf("\n");
    return 0;
}
分类: 文章

litble

苟...苟活者在淡红的血色中,会依稀看见微茫的希望

发表评论

电子邮件地址不会被公开。 必填项已用*标注

你是机器人吗? =。= *