(KMP)[NOI2014] 动物园解析

本文介绍了如何利用KMP算法解决NOI2014动物园问题,通过暴力跳转和倍增优化实现O(nlgn)的时间复杂度。文章详细阐述了如何计算字符串前缀的个数,并利用递增性质简化计算,最终得到答案。代码示例展示了算法的实现过程。
摘要由CSDN通过智能技术生成

(KMP)[NOI2014] 动物园解析

以前oi的时候没好好学的算法,大学再学一遍,收获还是蛮大的


本题要求num[i]+1的乘积,其中num[i]是对于一个字符串的第i个前缀所具有的长度不大于i/2的(前缀函数求的那种)前缀的个数。


  • 1
    当然先kmp,发现可以暴力跳转,也就是kmp求得 pi[i](或者称之为next[i])后暴力枚举pi[pi[i]-1],直到长度满足要求,复杂度为O(n2)。
    发现这个枚举很眼熟,可以通过倍增优化,时间复杂度降至O(nlgn)。

  • 2
    上面得到的是最大的满足要求的长度,而我们要求num。因为已经有了对应长度,这里我重新定义一下num,num[i]表示的是(前缀函数求的那种)前缀的个数(即去掉长度限制)
    发现num有递增性质,对任意j=pi[i],num[i]=num[j-1]+1,i.e.长度短的都符合条件,只是比j-1多了一个长一点的前缀。

  • 3
    对于 1 中求到的长度设为l,l-1就是对应的位置,所以对应数量由 2 得为num[l-1],所以答案就是这个num加1后得乘积咯。

  • 4
    代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
using namespace std;
const int mx=1000005,maxn=20,mod=1000000007;
char s[mx];
int pi[22][mx],num[mx];
int l;
ll ans;
int main()
{
   int t,p;scanf("%d",&t);
   for(int op=1;op<=t;++op){
   	scanf("%s",s);l=strlen(s);ans=1;
   	memset(pi,0,sizeof(pi));
   	for(int i=1;i<l;++i){
   		int j=pi[0][i-1];
   		while(j>0&&s[i]!=s[j]) j=pi[0][j-1];
   		if(s[i]==s[j]) ++j;
   		pi[0][i]=j;
   		if(j) num[i]=num[j-1]+1;
   		else num[i]=0;
   		if(j==0) p=0; 
   		else for(int k=1;k<=maxn;++k){
   			pi[k][i]=pi[k-1][pi[k-1][i]-1];
   			if(!pi[k][i]){
   				p=k-1;break;
   			}
   		}
   		j=i;
   		for(int k=p;k>=0;--k) if((pi[k][j]<<1)>i+1){
   			j=pi[k][j]-1;
   		}
   		ans*=(num[j]+1);ans%=mod;
   	}
   	printf("%lld\n",ans);
   }
   return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hiroxzwang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值