[KMP] NOI2014 动物园

传送门 - > \(bzoj3670\) 动物园

题目描述

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

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

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

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

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

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

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

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

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

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

输入输出格式

输入格式:

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

输出格式:

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

输入输出样例

输入样例#1:

3
aaaaa
ab
abcababc

输出样例#1:

36
1
32

说明

测试点编号

约定

  1. N ≤ 5, L ≤ 50
  2. N ≤ 5, L ≤ 200
  3. N ≤ 5, L ≤ 200
  4. N ≤ 5, L ≤ 10,000
  5. N ≤ 5, L ≤ 10,000
  6. N ≤ 5, L ≤ 100,000
  7. N ≤ 5, L ≤ 200,000
  8. N ≤ 5, L ≤ 500,000
  9. N ≤ 5, L ≤ 1,000,000
  10. N ≤ 5, L ≤ 1,000,000

    题解

这道题要求出前缀与后缀不重合的公共前后缀的数量,也就是长度小于等于原字符串长度一半的公共前后缀的数量,我们依然可以通过kmp的预处理,求出最长公共前后缀,然后暴力向前跳,直到跳到0,然后统计长度小于一半的数量,每次ans累乘,但是这样最坏是\(O(n^2)\)的, 无法接受,洛谷上50分

50分Code

#include<cstdio>
#include<string>
#include<iostream>
#include<cstring>
#include<cmath>
#define Min(a,b) (a)<(b)?(a):(b)
#define Max(a,b) (a)>(b)?(a):(b)
#define in(i) (i=read())
using namespace std;
typedef long long lol;
int read() {
  int ans=0,f=1; char i=getchar();
  while(i<'0' || i>'9') {if(i=='-') f=-1; i=getchar();}
  while(i>='0' && i<='9') {ans=(ans<<1)+(ans<<3)+i-'0'; i=getchar();}
  return ans*f;
}
const int mod=1e9+7;
lol ans=1,n,sum;
int nex[1000010];
char s[1000010];
void get() {
  int p=0;
  for(int i=1;i<n;i++) {
    while(p && s[p]!=s[i]) p=nex[p];
    if(s[p]==s[i]) nex[i+1]=(++p);
    else nex[i+1]=0;
  }
}
void match() {
  for(int i=n;i>=1;i--) {
    int p=i,sum=0;
    while(p) {
      if(p<=(i>>1)) sum++;
      p=nex[p];
    }
    if(nex[i]) ans=(ans*(sum+1)%mod)%mod;
  }
}
int main()
{
  int T; in(T);
  while(T--) {
    scanf("%s",s);
    n=strlen(s);ans=1;
    get(); match();
    printf("%lld\n",ans);
  }
  return 0;
}

那么我们想怎么优化呢,可不可以把\((n^2)\)变成\(O(nlogn)\)呢?显然是可以的,我们发现next[]数组是可以不断向前跳满足单调性的,所以可以使用倍增优化,\(next[i][j]\)表示i的前\(2^j\)个公共前后缀的位置,同时\(2 ^j\)也表示有多少个合法数量,方便我们统计,那么代码应该很容易打出来了

80分Code

#include<cstdio>
#include<string>
#include<iostream>
#include<cstring>
#include<cmath>
#define in(i) (i=read())
using namespace std;
typedef long long lol;
int read() {
  int ans=0,f=1; char i=getchar();
  while(i<'0' || i>'9') {if(i=='-') f=-1; i=getchar();}
  while(i>='0' && i<='9') {ans=(ans<<1)+(ans<<3)+i-'0'; i=getchar();}
  return ans*f;
}
const int mod=1e9+7;
lol ans=1,n,sum;
int nex[1000010][20];
char s[1000010];
void get() {
  int p=0;
  for(int i=1;i<n;i++) {
    while(p && s[p]!=s[i]) p=nex[p][0];
    if(s[p]==s[i]) nex[i+1][0]=(++p);
    else nex[i+1][0]=0;
  }
}
void init() {
  for(int j=1;j<=19;j++)
    for(int i=n;i>=1;i--)
      nex[i][j]=nex[nex[i][j-1]][j-1];
}
void match() {
  for(int i=n;i>=1;i--) {
    int p=i,sum=0;
    for(int j=19;j>=0;j--) 
      if(nex[p][j]>(i>>1)) p=nex[p][j];
    for(int k=19;k>=0;k--) {
      if(nex[p][k]) p=nex[p][k],sum|=1<<k;
    }
    ans=(ans*(sum+1)%mod)%mod;
  }
}
int main()
{
  int T; in(T);
  while(T--) {
    scanf("%s",s);
    n=strlen(s);ans=1;
    get(); init(); match();
    printf("%lld\n",ans);
  }
  return 0;
}

这样就到了80分,然后听了大佬的玄学优化,把数组小的那一维放前面,方便取地址

80分/bzojAC Code

\(bzoj - 5212ms\)

#include<bits/stdc++.h>
#define in(i) (i=read())
using namespace std;
typedef long long lol;
int read() {
  int ans=0,f=1; char i=getchar();
  while(i<'0' || i>'9') {if(i=='-') f=-1; i=getchar();}
  while(i>='0' && i<='9') {ans=(ans<<1)+(ans<<3)+i-'0'; i=getchar();}
  return ans*f;
}
const int mod=1e9+7;
lol ans=1;
int n,sum;
int nex[20][1000010];
char s[1000010];
void get() {
  int p=0;
  for(int i=1;i<n;++i) {
    while(p && s[p]!=s[i]) p=nex[0][p];
    if(s[p]==s[i]) nex[0][i+1]=(++p);
    else nex[0][i+1]=0;
  }
}
void init() {
  for(int j=1;j<=18;++j)
    for(int i=n;i>=1;--i)
      nex[j][i]=nex[j-1][nex[j-1][i]];
}
void match() {
  for(int i=n;i>=1;--i) {
    int p=i,sum=0;
    for(int j=18;j>=0;--j)
      if(nex[j][p]>(i>>1)) p=nex[j][p];
    for(int k=18;k>=0;--k) 
      if(nex[k][p]) p=nex[k][p],sum|=1<<k;
    ans=(ans*(sum+1)%mod)%mod;
  }
}
int main()
{
  int T; in(T);
  while(T--) {
    scanf("%s",s);
    n=strlen(s);ans=1;
    get(); init(); match();
    printf("%lld\n",ans);
  }
  return 0;
}

玄学玄学
其实还是可以优化,我们可以先预处理一个cnt[i]数组,代表到1~i有多少个公共前缀,很明显cnt[p]=cnt[next[p]]+1
先想最暴力的方法,从当前位置开始一直向前跳,但这样最坏复杂度是\(O(n^2)\),和暴力一样了
那么我们模拟kmp的匹配方法,接着上次位置往前跳,跳到第一个小于等于i的一半的地方,直接用cnt[p]去统计就可以了,这样近似复杂度\(O(n)\)
跑的贼快

AC Code

\(bzoj - 972ms\)

#include<bits/stdc++.h>
#define in(i) (i=read())
using namespace std;
typedef long long lol;
int read() {
  int ans=0,f=1; char i=getchar();
  while(i<'0' || i>'9') {if(i=='-') f=-1; i=getchar();}
  while(i>='0' && i<='9') {ans=(ans<<1)+(ans<<3)+i-'0'; i=getchar();}
  return ans*f;
}
const int mod=1e9+7;
lol ans=1;
int n,sum;
int nex[1000010],cnt[1000010];
char s[1000010];
void get() {
  int p=0;
  for(int i=1;i<n;++i) {
    while(p && s[p]!=s[i]) p=nex[p];
    if(s[p]==s[i]) nex[i+1]=(++p);
    else nex[i+1]=0;
  }
}
void match() {
  cnt[1]=1;
  for(int i=2;i<=n;++i) cnt[i]=cnt[nex[i]]+1;
  for(int i=0,p=0;i<n;++i) {
    while(p && s[p]!=s[i]) p=nex[p];
    p+=(s[p]==s[i]);
    while(p>(i+1>>1)) p=nex[p];
    ans=(ans*(cnt[p]+1)%mod)%mod;
  }
}
int main()
{
  int T; in(T);
  while(T--) {
    scanf("%s",s);
    n=strlen(s);ans=1;
    get(); match();
    printf("%lld\n",ans);
  }
  return 0;
}

博主蒟蒻,随意转载.但必须附上原文链接

http://www.cnblogs.com/real-l/

猜你喜欢

转载自www.cnblogs.com/real-l/p/9486897.html