• [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;
    }
    
  • 相关阅读:
    HDU1548:A strange lift
    hdu1978_How many ways dfs+记忆化搜索
    HDU1518:Square(DFS)
    HDU ACM 1495 非常可乐(广搜BFS)
    HDU1372:Knight Moves(BFS)
    662_二叉树最大宽度
    590_N叉树的后序遍历
    一直在坚持
    动态链接库DLL的操作顺序
    最近木马的查杀方法/各类杀毒软件的使用
  • 原文地址:https://www.cnblogs.com/Vikyanite/p/15458975.html
Copyright © 2020-2023  润新知