洛谷2375 NOI2014动物园(KMP)

题目链接:
题目.

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

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

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

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

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

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

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

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

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

QwQ然后就直接像 K M P 那种跳的方式,跳 j 就可以的

感觉这个题很有意思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;
}

猜你喜欢

转载自blog.csdn.net/y752742355/article/details/82152155
今日推荐