BZOJ3670: [Noi2014]动物园(DP)

11 篇文章 0 订阅
6 篇文章 0 订阅

前言

这道题理解KMP真是再好不过了,虽然和KMP没什么关系。
但是这里核心就是处理KMP的next数组,而KMP的重难点也是next数组,所以对于刚刚学了KMP的少主来说真的是很恰到好处了。

题意

求出一个序列每一个前缀的不重合的相同前缀和后缀的个数。
看不懂吧。。。。看这个
题目链接

分析

虽然说和KMP关系密切并且你必须会KMP,但实际上分析和KMP没啥关系。
大概就是KMP的next数组有一个含义,而且题目给得很明确:next[i]就是在长度为[i]的字符串中前缀=后缀的最长长度。
有了后只需要不停地next,把满足题意的情况记下来即可。
直接暴力找肯定要TLE,亲测。
由于需要有长度限制,对于长度为i的字符串,找到了第一个长度在条件内的,那么剩下的长度都满足,就可以DP
但是如果暴力找第一个长度为i的字符串还是会超时,我们可以根据i比较大的找到的第一个满足条件的数一定比i比较小的找到的第一个满足条件的数大,直接上大步跳跃即可,慢了一点点而已。

如果不大步跳跃,也可以在求出了fail(next)数组后,用类似的方法求出满足长度要求的一个新的f数组,f数组就是第一个满足长度的条件,剩下的仍然是DP,因为没有递归要快一些。

顺便,memset其实是非常消耗时间的一个操作。

代码

//两个solve选一个即可

#include<cctype>
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int maxn=1e6+105,mod=1e9+7;
int n;
int num,fail[maxn],d[maxn],f[maxn];
char s[maxn];
int ff(int i,int n)
{
    if(i*2<=n)return i;
    return f[i]=ff(f[i],n);
}
void solve()//大步跳跃
{
    gets(s);
    int ans=1;
    int n=strlen(s);
    int i=0,j=-1;
    fail[0]=-1;
    while(i<n)
    {
        while(j>=0 && s[i]!=s[j])j=fail[j];
        i++,j++;
        fail[i]=j;
    }
    for(int i=1;i<=n;i++)
    {
        d[i]=d[fail[i]]+1;
        f[i]=fail[i];
    }
    for(int i=n;i>=1;i--)
    {
        f[i]=ff(f[i],i);
        ans=1ll*ans*(d[f[i]]+1)%mod;
    }
    printf("%d\n",ans);
}
void solve()//再次递推求f
{
    gets(s);
    int ans=1;
    int n=strlen(s);
    int i=0,j=-1;
    fail[0]=-1;
    while(i<n)
    {
        while(j>=0 && s[i]!=s[j])j=fail[j];
        i++,j++;
        fail[i]=j;
    }
    i=0,j=-1;
    while(i<n)
    {
        while(j>=0 && ( s[i]!=s[j] || 2*(j+1)>(i+1) ))j=fail[j];
        i++,j++;
        f[i]=j;
    }
    for(int i=1;i<=n;i++)
    {
        d[i]=d[fail[i]]+1;
        ans=1ll*ans*(d[f[i]]+1)%mod;
    }
    printf("%d\n",ans);
}
int main()
{
    //freopen("in.txt","r",stdin);
    scanf("%d",&n);
    gets(s);
    while(n--)
        solve();
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值