Loading

[luoguP2375] [NOI2014] 动物园

题目链接

其实题意还挺难理解的。。。

其实就是定义了一个数组,叫做 \(num[i]\)

假设字符串为 \(s = abcababc\)

那么\(num[4] = 1\), 因为在前缀子串\(s[1, 4] = abca\) (我这里下标是从1开始)中后缀\(a\)与前缀\(a\)相同所以为1

假设字符串为 \(s = aaaa\)

那么\(num[4] = 2\), 虽然\(s[1, 4] = aaaa\) 拥有\(a, aa, aaa, aaaa\)四个前缀,和\(aaaa, aaa, aa, a\)四个后缀
但是\(aaa\)前缀与\(aaa\)后缀有重叠部分所以不算,\(aaaa\)同理,之后\(aa\)\(a\)

(做完这道题之后我对kmp的next数组的理解大大提升了!)

首先我们先要知道next数组到底是在干嘛。
我们先从\(next\)数组的目的是什么出发,\(next\)数组是为了减少失配时的重新匹配数。
就相当于失配时,是将匹配串移动\(next[i]\)的距离而不是仅仅移动一格进行重新匹配。

为什么可以移动\(next[i]\)的距离?因为\(next[i]\)的定义就是\([1, i]\)的子串的后缀与前缀的最长匹配长度。(因为下标从1开始,所以长度就其实是下标)

也就是说这段后缀与前缀的一段是完全相等的。

所以如果这个位置的下一个位置失配了,我们就可以跳到前缀中与这一段完全相等的位置也就是\(next[i]\),从而减少重新匹配的次数,达到线性复杂度。

于是我们重新看这道题,这道题求的是\([1, i]\)的字符串中的后缀与前缀相同但不重叠数的后缀数。

我们只知道\(next[i]\)的定义,但是next数组跟这个似乎完全扯不上关系啊。

这里就比较思维了, 首先如果\(next[i] != 0\)的话,说明这个子串肯定有为\([1, next[i]]\)的公共前后缀,
也肯定有\([1, next[next[i]]]\)的公共前后缀,.....于是我们发现,这个不就是我们要求的重叠的\(num[i]\)吗,所以就是求多少次到next[i]为0。

这里可以举个例子,假设$s[1, 13] = abcabcdabcabc$, $next[13] = 6$
所以$[1, 13]$有$[1, 6]$的公共前后缀,也会有$[1, 3]$的公共前后缀。是不是很神奇!
(其实我们从next数组的定义出发就能发现这个规律了,但是我想了好久,还是太菜了www)

之后我们来解决不重叠的问题,我们考虑如何不重叠,不就是公共前后缀的长度小于等于 \(i/2\)也就是 \(next[j] <= i/2\)不就行了吗

但是暴力跳肯定会t的,于是我们考虑优化,我们是否需要每次都将指针从当前位置出发?
仔细思索了一下,j其实不需要每次都从当前i位置出发,只需要跟求next数组的时候一样就好了,当\(s[j+1]==s[i]\)的时候,就把\(j++\),之后当他大于 \(i/2\) 的时候我们就让\(j = next[j]\)就好了。

当前的\(num[i] = num[j] + 1\)

之后统计答案即可

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6+10;
char s[maxn];
int nxt[maxn], num[maxn];
const int mod = 1e9+7;
int main() {
    int T; scanf("%d", &T);
    while (T--) {
        scanf("%s", s+1);
        int n = strlen(s+1);
        num[1] = 1;
        for (int i = 2, j = 0; i <= n; ++ i) {
            while (j && s[j+1] != s[i]) j = nxt[j];
            if (s[j+1] == s[i]) j ++;
            nxt[i] = j;
            //if(j)    关于这里为什么不用这句可以想一下
            num[i] = num[j] + 1; 
        }
        long long ans = 1;
        for (int i = 2, j = 0; i <= n; ++ i) {
            while (j && s[j+1] != s[i]) j = nxt[j];
            if (s[j+1] == s[i]) j ++;
            while (j*2 > i) j = nxt[j];
            ans = ans * (num[j] + 1) % mod;
        }
        cout << ans << endl;
    }
    return 0;
}
posted @ 2021-10-25 16:44  ViKyanite  阅读(28)  评论(0编辑  收藏  举报