题目大意
题目首先花了大量篇幅介绍了KMP算法。其中包括回退数组 n e x t next next:
- 对于字符串 S S S的前 i i i个字符构成的子串,既是它的后缀又是它的前缀的字符串中(它本身除外),最长的长度记作 n e x t [ i ] next[i] next[i]。
- 例如 S S S为 a b c a b a b c abcababc abcababc,则 n e x t [ 5 ] = 2 next[5]=2 next[5]=2。因为 S S S的前 5 5 5个字符为 a b c a b abcab abcab, a b ab ab既是它的后缀又是它的前缀,并且找不到一个更长的字符串满足这个性质。同理,还可得出 n e x t [ 1 ] = n e x t [ 2 ] = n e x t [ 3 ] = 0 next[1]=next[2]=next[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 next[4]=next[6]=1, n e x t [ 7 ] = 2 next[7]=2 next[7]=2, n e x t [ 8 ] = 3 next[8]=3 next[8]=3。
而本题要求求出一个 n u m num num数组:
- 对于字符串 S S S的前 i i i个字符构成的子串,既是它的后缀同时又是它的前缀,并且该后缀与该前缀不重叠,将这种字符串的数量记作 n u m [ i ] num[i] num[i]。
- 例如 S S S为 a a a a a aaaaa aaaaa,则 n u m [ 4 ] = 2 num[4]=2 num[4]=2。这是因为 S S S的前 4 4 4个字符为 a a a a aaaa aaaa,其中 a a a和 a a aa aa都满足性质“既是后缀又是前缀”,同时保证这个后缀与这个前缀不重叠。而 a a a aaa aaa虽然满足性质“既是后缀又是前缀”,但是这个后缀与这个前缀重叠了,所以不能计算在内。同理, n u m [ 1 ] = 0 num[1]=0 num[1]=0, n u m [ 2 ] = n u m [ 3 ] = 1 num[2]=num[3]=1 num[2]=num[3]=1, n u m [ 5 ] = 2 num[5]=2 num[5]=2。
字符串的长度很大( L ⩽ 1000000 L\leqslant 1000000 L⩽1000000),结果输出 ∏ i = 1 L ( n u m [ i ] + 1 ) m o d 1000000007 \prod\limits_{i=1}^L(num[i]+1)\mod 1000000007 i=1∏L(num[i]+1)mod1000000007。
思路
n u m num num数组中“后缀与前缀不重叠”的条件不容易处理,并且跟 n e x t next next数组的定义大相径庭,故定义一个 n u m ′ num' num′数组:
- 对于字符串 S S S的前 i i i个字符构成的子串,既是它的后缀同时又是它的前缀的字符串的数量记作 n u m [ i ] num[i] num[i]。这里的前缀和后缀包括 S S S本身。
其实就是去掉“后缀与前缀不重叠”这个条件的 n u m num num数组。
n u m ′ [ i ] num'[i] num′[i]中统计的串中,首先一定有这个串本身;而其它的串都被囊括在 n e x t [ i ] next[i] next[i]表示的串内(包括两个长度为 n e x t [ i ] next[i] next[i]的串):
根据
n
e
x
t
next
next数组的定义,首尾两个长度为
n
e
x
t
[
i
]
next[i]
next[i]的串相同,把所有的串平移至同一个长度为
n
e
x
t
next
next的串内:
这些串就成了前
n
e
x
t
[
i
]
next[i]
next[i]个字符构成的子串中既是后缀又是前缀的字符串,根据定义,这些串的数量为
n
u
m
′
[
n
e
x
t
[
i
]
]
num'[next[i]]
num′[next[i]]。
再加上前
i
i
i个字符构成的子串本身,得出递推式:
n
u
m
′
[
i
]
=
n
u
m
′
[
n
e
x
t
[
i
]
]
+
1
num'[i]=num'[next[i]]+1
num′[i]=num′[next[i]]+1
递推的时间复杂度是 O ( n ) O(n) O(n)(这里以及下文的 n n n指字符串长度 L L L,也就是 n u m num num数组的大小)。递推的边界是 n u m ′ [ 0 ] = 0 num'[0]=0 num′[0]=0。
现在考虑把“后缀与前缀不重叠”的条件加上。这个条件就是让
n
u
m
[
i
]
num[i]
num[i]中的串的长度不超过
i
2
\frac i2
2i。
为了让字符串既是后缀又是前缀,除了串本身以外,最长的字符串就是
n
e
x
t
[
i
]
next[i]
next[i]表示的串。若
n
e
x
t
[
i
]
next[i]
next[i]的长度已经不超过
i
i
i的一半,则
n
u
m
[
i
]
num[i]
num[i]就是
n
u
m
′
[
n
e
x
t
[
i
]
]
num'[next[i]]
num′[next[i]]。若
n
e
x
t
[
i
]
next[i]
next[i]仍不满足要求,根据前面的平移操作和递推,下一个最长的串的长度就应该是
n
e
x
t
[
n
e
x
t
[
i
]
]
next[next[i]]
next[next[i]],一直回退下去,直到串的长度为满足条件的最大的长度
j
j
j。然后就有
n
u
m
[
i
]
=
n
u
m
′
[
j
]
num[i]=num'[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)
O(n2)。不妨试试样例的第一组数据:
a
a
a
a
a
aaaaa
aaaaa。
造成如此高的复杂度的原因是
n
e
x
t
[
i
]
next[i]
next[i]的规模是
O
(
n
)
O(n)
O(n)的,最坏情况下对于每个
i
i
i,
j
j
j都要减小
O
(
n
)
O(n)
O(n)次,然后就爆了。
考虑用KMP求 n e x t next 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)
O(n)的,因为
i
i
i从
1
1
1枚举到
n
n
n,增加
O
(
n
)
O(n)
O(n)次;除了if
语句中
j
j
j增加
O
(
n
)
O(n)
O(n)次,其余时间
j
j
j都在减少,所以
j
j
j减少也不会超过
O
(
n
)
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
i的枚举规模仍然是
O
(
n
)
O(n)
O(n),
j
j
j还是在if
语句处增加
O
(
n
)
O(n)
O(n)次,因此时间复杂度为
O
(
n
)
O(n)
O(n)。
j j j是否是可行解(是否存在长度为 j j j的串既是后缀又是前缀)?
j j j是由 n e x t next next数组递推得到的,根据前面的递推, j j j是可行解。
j j j是否是最优解( j j j是否是不超过 i 2 \frac i2 2i的最大可行解)?
由于
j
j
j在当前的
i
i
i时要回退减小(第二个while
语句),
i
i
i增加
1
1
1时,
j
j
j似乎就有可能不是最大的可行解。
根据前面得知,
j
j
j从
n
e
x
t
[
i
]
next[i]
next[i]处回退一定不会错过最优解。若
j
j
j不继续回退,得到的
j
j
j就是
n
e
x
t
[
i
]
next[i]
next[i],不会错过最优解。现在
j
j
j要继续回退,轮到下一个
i
′
=
i
+
1
i'=i+1
i′=i+1时,为了当前子串的长度为
j
′
j'
j′的前后缀匹配,
j
j
j原本就要回退(第一个while
语句)。
- 若此次回退后
j
′
j'
j′没有超过
i
′
2
\frac{i'}2
2i′,就有
j
′
⩽
i
+
1
2
j'\leqslant\frac{i+1}2
j′⩽2i+1。
- i i i为偶数时, j ′ ⩽ i 2 j'\leqslant\frac i2 j′⩽2i,因此在这之前把 j j j回退到 i 2 \frac i2 2i就没有什么影响。
-
i
i
i为奇数时,
- 若 j ′ ⩽ i − 1 2 j'\leqslant\frac{i-1}2 j′⩽2i−1,则在这之前把 j j j回退到 i 2 \frac i2 2i也没有什么影响。
- 若 j ′ = i + 1 2 j'=\frac{i+1}2 j′=2i+1,说明可以匹配的最长的前后缀刚好是子串的一半,根据KMP算法的原理,此时已匹配的长度 j ′ j' j′一定是由上一次的 j j j加 1 1 1得来的。于是 j = i − 1 2 j=\frac{i-1}2 j=2i−1,同样没有影响。
- 若此次回退后 j ′ j' j′超过了 i ′ 2 \frac{i'}2 2i′,为了得到 n u m [ i ′ ] num[i'] num[i′]的值, j ′ j' j′仍然要回退,回到上一种情况。