[Luogu P2375] [NOI2014]动物园

本文介绍了一个基于KMP算法的字符串处理问题,旨在求解一个名为num的数组,该数组记录了字符串前缀与后缀特定匹配情况的数量。文章通过实例解释了next数组的概念,并提供了一个高效的解决方案来求解num数组。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

近日,园长发现动物园中好吃懒做的动物越来越多了。例如企鹅,只会卖萌向游客要吃的。为了整治动物园的不良风气,让动物们凭自己的真才实学向游客要吃的,园长决定开设算法班,让动物们学习算法。

某天,园长给动物们讲解KMPKMP算法。

园长:“对于一个字符串 SS ,它的长度为 L 。我们可以在 O(L)O(L)的时间内,求出一个名为nextnext的数组。有谁预习了nextnext数组的含义吗?”

熊猫:“对于字符串 SS的前 ii 个字符构成的子串,既是它的后缀又是它的前缀的字符串中(它本身除外),最长的长度记作 next[i] 。”

园长:“非常好!那你能举个例子吗?”

熊猫:“例 SSabcababc,则 next[5]=2next[5]=2 。因为 SS 的前 5 个字符为abcababcababab既是它的后缀又是它的前缀,并且找不到一个更长的字符串满足这个性质。同理,还可得出next[1]=next[2]=next[3]=0next[4]=next[6]=1next[7]=2,next[8]=3next[1]=next[2]=next[3]=0,next[4]=next[6]=1,next[7]=2,next[8]=3 。”

园长表扬了认真预习的熊猫同学。随后,他详细讲解了如何在 O(L)O(L)的时间内求出nextnext数组。

下课前,园长提出了一个问题:“KMPKMP算法只能求出nextnext数组。我现在希望求出一个更强大numnum数组一一对于字符串 SS 的前i 个字符构成的子串,既是它的后缀同时又是它的前缀,并且该后缀与该前缀不重叠,将这种字符串的数量记作 num[i]num[i] 。例如 SSaaaaa,则 num[4]=2num[4]=2 。这是因为 SS 的前 4 个字符为aaaa,其中aaaa都满足性质‘既是后缀又是前缀’,同时保证这个后缀与这个前缀不重叠。而aaaaaa虽然满足性质‘既是后缀又是前缀’,但遗憾的是这个后缀与这个前缀重叠了,所以不能计算在内。同理,num[1]=0,num[2]=num[3]=1,num[5]=2num[1]=0,num[2]=num[3]=1,num[5]=2 。”

最后,园长给出了奖励条件,第一个做对的同学奖励巧克力一盒。听了这句话,睡了一节课的企鹅立刻就醒过来了!但企鹅并不会做这道题,于是向参观动物园的你寻求帮助。你能否帮助企鹅写一个程序求出 numnum数组呢?

特别地,为了避免大量的输出,你不需要输出 num[i]num[i] 分别是多少,你只需要输出所有(num[i]+1)(num[i]+1)的乘积,对 1,000,000,0071,000,000,007 取模的结果即可。

输入输出格式

输入格式:

第 1 行仅包含一个正整数 nn ,表示测试数据的组数。
随后 n 行,每行描述一组测试数据。每组测试数据仅含有一个字符串 SSS 的定义详见题目描述。数据保证 SS 中仅含小写字母。输入文件中不会包含多余的空行,行末不会存在多余的空格。

输出格式:

包含 n 行,每行描述一组测试数据的答案,答案的顺序应与输入数据的顺序保持一致。对于每组测试数据,仅需要输出一个整数,表示这组测试数据的答案对 1,000,000,0071,000,000,007取模的结果。输出文件中不应包含多余的空行。

输入输出样例

输入样例#1:
3
aaaaa
ab
abcababc

输出样例#1:

36
1
32 

解题分析

首先我们发现在求出nextnext数组时可以顺带求出弱化版的numnum数组: 长度可超过i2i2的总数。现在考虑如何去掉这些不合法的情况。

显然对于每个num[i]num[i]我们不能暴力向前跳, 因为如果数据全是aa的话, next[i]将会等于i1i−1, 一下子被卡到了O(N2)O(N2)。 所以我们可以参照nextnext数组的确定方法, 每次从上一个找到的位置向后跳, 即可大大优化时间复杂度至O(N)。

感觉NOI2014是真的水...蒟蒻博主似乎能A3道题
代码如下:

#include <cstdio>
#include <cstring>
#define R register
#define IN inline
#define gc getchar()
#define W while
#define MX 1000005
#define ll long long
#define MOD 1000000007
int tim, ans[MX], len, nex[MX], now, pre;
char buf[MX];
int main(void)
{
    scanf("%d", &tim);
    W(tim--)
    {
        scanf("%s", buf);
        memset(nex, 0, sizeof(nex));
        len = std::strlen(buf);
        ans[0] = 0; ans[1] = 1; now = 1, pre = 0;
        W (now < len)//筛出nex数组
        {
            W (pre && buf[now] != buf[pre]) pre = nex[pre];
            pre += (buf[now] == buf[pre]), nex[now + 1] = pre, ans[now + 1] = ans[pre] + 1, ++now;
        }
        ll ret = 1;
        now = 1, pre = 0;
        int bound;
        W (now < len)
        {//pre沿着之前的pre继续跳, 因为肯定前面的pre的位置不会超过now/2
            bound = now + 1;
            W (pre && buf[now] != buf[pre]) pre = nex[pre];
            pre += (buf[now] == buf[pre]);
            W ((pre << 1) > bound) pre = nex[pre];//跳多了, 往前跳
            ret = ret * (ans[pre] + 1) % MOD;
            ++now;
        }
        printf("%lld\n", ret);
        }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值