您的位置:首页 > 其它

BZOJ3670: [Noi2014]动物园(DP)

2018-03-06 23:28 148 查看

前言

这道题理解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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: