【题目】NOI2014 动物园

题目大意

题目首先花了大量篇幅介绍了KMP算法。其中包括回退数组 n e x t next

  • 对于字符串 S S 的前 i i 个字符构成的子串,既是它的后缀又是它的前缀的字符串中(它本身除外),最长的长度记作 n e x t [ i ] next[i]
  • 例如 S S a b c a b a b c abcababc ,则 n e x t [ 5 ] = 2 next[5]=2 。因为 S S 的前 5 5 个字符为 a b c a b abcab a b ab 既是它的后缀又是它的前缀,并且找不到一个更长的字符串满足这个性质。同理,还可得出 n e x t [ 1 ] = n e x t [ 2 ] = n e x t [ 3 ] = 0 next[1]=next[2]=next[3]=0 n e x t [ 4 ] = n e x t [ 6 ] = 1 next[4]=next[6]=1 n e x t [ 7 ] = 2 next[7]=2 n e x t [ 8 ] = 3 next[8]=3

而本题要求求出一个 n u m num 数组:

  • 对于字符串 S S 的前 i i 个字符构成的子串,既是它的后缀同时又是它的前缀,并且该后缀与该前缀不重叠,将这种字符串的数量记作 n u m [ i ] num[i]
  • 例如 S S a a a a a aaaaa ,则 n u m [ 4 ] = 2 num[4]=2 。这是因为 S S 的前 4 4 个字符为 a a a a aaaa ,其中 a a a a aa 都满足性质“既是后缀又是前缀”,同时保证这个后缀与这个前缀不重叠。而 a a a aaa 虽然满足性质“既是后缀又是前缀”,但是这个后缀与这个前缀重叠了,所以不能计算在内。同理, n u m [ 1 ] = 0 num[1]=0 n u m [ 2 ] = n u m [ 3 ] = 1 num[2]=num[3]=1 n u m [ 5 ] = 2 num[5]=2

字符串的长度很大( L 1000000 L\leqslant 1000000 ),结果输出 i = 1 L ( n u m [ i ] + 1 ) m o d    1000000007 \prod\limits_{i=1}^L(num[i]+1)\mod 1000000007


思路

n u m num 数组中“后缀与前缀不重叠”的条件不容易处理,并且跟 n e x t next 数组的定义大相径庭,故定义一个 n u m num' 数组:

  • 对于字符串 S S 的前 i i 个字符构成的子串,既是它的后缀同时又是它的前缀的字符串的数量记作 n u m [ i ] num[i] 。这里的前缀和后缀包括 S S 本身。

其实就是去掉“后缀与前缀不重叠”这个条件的 n u m num 数组。

n u m [ i ] num'[i] 中统计的串中,首先一定有这个串本身;而其它的串都被囊括在 n e x t [ i ] next[i] 表示的串内(包括两个长度为 n e x t [ i ] next[i] 的串):


根据 n e x t next 数组的定义,首尾两个长度为 n e x t [ i ] next[i] 的串相同,把所有的串平移至同一个长度为 n e x t next 的串内:

这些串就成了前 n e x t [ i ] next[i] 个字符构成的子串中既是后缀又是前缀的字符串,根据定义,这些串的数量为 n u m [ n e x t [ i ] ] num'[next[i]]
再加上前 i i 个字符构成的子串本身,得出递推式:
n u m [ i ] = n u m [ n e x t [ i ] ] + 1 num'[i]=num'[next[i]]+1

递推的时间复杂度是 O ( n ) O(n) (这里以及下文的 n n 指字符串长度 L L ,也就是 n u m num 数组的大小)。递推的边界是 n u m [ 0 ] = 0 num'[0]=0

现在考虑把“后缀与前缀不重叠”的条件加上。这个条件就是让 n u m [ i ] num[i] 中的串的长度不超过 i 2 \frac i2
为了让字符串既是后缀又是前缀,除了串本身以外,最长的字符串就是 n e x t [ i ] next[i] 表示的串。若 n e x t [ i ] next[i] 的长度已经不超过 i i 的一半,则 n u m [ i ] num[i] 就是 n u m [ n e x t [ i ] ] num'[next[i]] 。若 n e x t [ i ] next[i] 仍不满足要求,根据前面的平移操作和递推,下一个最长的串的长度就应该是 n e x t [ n e x t [ i ] ] next[next[i]] ,一直回退下去,直到串的长度为满足条件的最大的长度 j j 。然后就有
n u m [ i ] = n u m [ j ] num[i]=num'[j]

不难得到下面的代码:

for(int i=1;i<=n;i++){
  int j=next[i];
  while(j*2>i)j=next[j];
  num[i]=num'[j];
}

然而这个做法的时间复杂度是 O ( n 2 ) O(n^2) 。不妨试试样例的第一组数据: a a a a a aaaaa
造成如此高的复杂度的原因是 n e x t [ i ] next[i] 的规模是 O ( n ) O(n) 的,最坏情况下对于每个 i i j j 都要减小 O ( n ) O(n) 次,然后就爆了。

考虑用KMP求 n e x t next 数组的过程:

for(int i=2,j=0;i<=len;i++){
  while(j&&t[j+1]!=t[i])j=fail[j];
  if(t[j+1]==t[i])j++;fail[i]=j;
}

KMP的时间复杂度是 O ( n ) O(n) 的,因为 i i 1 1 枚举到 n n ,增加 O ( n ) O(n) 次;除了if语句中 j j 增加 O ( n ) O(n) 次,其余时间 j j 都在减少,所以 j j 减少也不会超过 O ( n ) O(n) 次。

于是可以把上面的暴力修改成下面的代码:

for(int i=2,j=0;i<=len;i++){
  while(j&&t[j+1]!=t[i])j=fail[j];
  if(t[j+1]==t[i])j++;
  while(j*2>i)j=fail[j];
  num[i]=num'[j];
}

几个问题:

时间复杂度?

i i 的枚举规模仍然是 O ( n ) O(n) j j 还是在if语句处增加 O ( n ) O(n) 次,因此时间复杂度为 O ( n ) O(n)

j j 是否是可行解(是否存在长度为 j j 的串既是后缀又是前缀)?

j j 是由 n e x t next 数组递推得到的,根据前面的递推, j j 是可行解。

j j 是否是最优解( j j 是否是不超过 i 2 \frac i2 的最大可行解)?

由于 j j 在当前的 i i 时要回退减小(第二个while语句), i i 增加 1 1 时, j j 似乎就有可能不是最大的可行解。
根据前面得知, j j n e x t [ i ] next[i] 处回退一定不会错过最优解。若 j j 不继续回退,得到的 j j 就是 n e x t [ i ] next[i] ,不会错过最优解。现在 j j 要继续回退,轮到下一个 i = i + 1 i'=i+1 时,为了当前子串的长度为 j j' 的前后缀匹配, j j 原本就要回退(第一个while语句)。

  • 若此次回退后 j j' 没有超过 i 2 \frac{i'}2 ,就有 j i + 1 2 j'\leqslant\frac{i+1}2
    • i i 为偶数时, j i 2 j'\leqslant\frac i2 ,因此在这之前把 j j 回退到 i 2 \frac i2 就没有什么影响。
    • i i 为奇数时,
      • j i 1 2 j'\leqslant\frac{i-1}2 ,则在这之前把 j j 回退到 i 2 \frac i2 也没有什么影响。
      • j = i + 1 2 j'=\frac{i+1}2 ,说明可以匹配的最长的前后缀刚好是子串的一半,根据KMP算法的原理,此时已匹配的长度 j j' 一定是由上一次的 j j 1 1 得来的。于是 j = i 1 2 j=\frac{i-1}2 ,同样没有影响。
  • 若此次回退后 j j' 超过了 i 2 \frac{i'}2 ,为了得到 n u m [ i ] num[i'] 的值, j j' 仍然要回退,回到上一种情况。
发布了26 篇原创文章 · 获赞 13 · 访问量 4880

猜你喜欢

转载自blog.csdn.net/PHenning/article/details/99343007