洛谷2375 NOI2014动物园(KMP)

题目链接:
题目.

简单一点来说,这个题就是求一个字符串的 num n u m 数组的和,其中有 num[i] n u m [ i ] 表示1~i中有多少个不交叉的相等的前缀和后缀 的数目,要求一个 O(n) O ( n ) 的做法

QwQ
感觉一看到这个题,其实没什么思路呀

KMP K M P 的角度出发,对于一个 i i 来说,显然pre[i],pre[pre[i]]都是他的后缀,所以,我们貌似可以维护一个 num1 n u m 1 表示,可以交叉的 相等的 前缀和后缀的数目

比较容易推出 num1[i]=num1[pre[i]]+1 n u m 1 [ i ] = n u m 1 [ p r e [ i ] ] + 1

这里可以理解成 在原来最多的数目上,再加上当前位匹配的贡献

因为你对于当前的i的一些前后缀的比较和计算,已经在 pre[i] p r e [ i ] 的时候计算过了

那么我们怎么计算这个题目要求的 num n u m
QwQ实际上对于每个 i i 要找到一个小于i2的最小递归层数的 pre p r e (递归的意思是 pre[pre[pre[i]]] p r e [ p r e [ p r e [ i ] ] ] )

如果我们对于每一个 i i 都暴力去做的话,时间复杂度肯定是不允许的,这时候我们就需要考虑一个性质:

我们在循环到i时当前的 j j 是不是可以重前一次的最长的不重叠的j得到呢?答案是肯定的,要么小于等于上一次的 j j ,要么等于上一次的j+1
具体证明可以通过反证法来证明

QwQ然后就直接像 KMP K M P 那种跳的方式,跳 j j <script type="math/tex" id="MathJax-Element-1792">j</script>就可以的

感觉这个题很有意思QwQ而且我还不是很懂呀

留个坑吧

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>

using namespace std;

inline int read()
{
   int x=0,f=1;char ch=getchar();
   while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
   while (isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
   return x*f;
}

const long long mod = 1e9+7;
const int maxn = 1e6+1e2;

int pre[maxn],num[maxn];
char s[maxn];
char s1[maxn];
long long ans=0;
int n;
int t;

void init()
{
    for (int i=1;i<=maxn-10;i++) s[i]=s1[i];
    memset(pre,0,sizeof(pre));
    ans=1;
    memset(num,0,sizeof(num));                                             
}

int main()
{
  cin>>t;
  while (t--)
  {
      init();
      scanf("%s",s+1);
      n=strlen(s+1);
      pre[1]=0;
      num[1]=1;  
      for (int i=2;i<=n;i++)
      {
          int j=pre[i-1];
          while (j && s[j+1] !=s[i]) j=pre[j];
          if (s[j+1]==s[i]) j++;
          pre[i]=j;
          num[i]=num[j]+1;
      }
      int j=0;
      for (int i=1;i<=n;i++)
      {
         while (j && s[j+1] !=s[i]) j=pre[j];
         if (s[j+1]==s[i]) j++;
         while ((j << 1)>=i+1) j=pre[j];
         ans=ans*(num[j]+1)%mod;
      }
      cout<<ans<<endl;
  }                                                                     
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值