题目大意:
定义F(n)如下(下面的0、1表示01串“0”、“1”,加法表示连接):![](https://img-blog.csdnimg.cn/20200111173507838.png)
求F(n)(n≤100)中非空01串p(|p|<105)出现的次数。
既然p是“串”,那么我们就用“串”的格式来保存它。定义count_p(s)为在字符串s中p出现的次数,则我们可以用递推。递推公式是:
f
(
n
)
=
f
(
n
−
1
)
+
f
(
n
−
2
)
+
f(n)=f(n-1)+f(n-2)+
f(n)=f(n−1)+f(n−2)+“跨区域的串”个数。我们用
O
(
∣
s
∣
)
O(|s|)
O(∣s∣)算法求出count_p(s)可以让速度大大提升。如果用
O
(
∣
s
∣
2
)
O(|s|^2)
O(∣s∣2)算法,说不定就TLE了。
接下来的任务是求“跨区域的串”的个数。我们可以求出它们可能出现的位置(即起码有一个元素在左边,起码有一个元素在右边),然后用count_p运行这个区间,求出这个个数。
代码中用s1表示
f
(
n
−
2
)
f(n-2)
f(n−2),用s2表示
f
(
n
−
1
)
f(n-1)
f(n−1),用s3表示
f
(
n
)
f(n)
f(n)。
最后用指针交换,就可以用这些指针轮流使用3个z字符串而不用每次重置。
代码如下:
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#define llong long long
using namespace std;
char p[100005],z1[400005],z2[400005],z3[400005];
int n,m,f[100005];llong ans[105];
int count_p(const char*s){
int j=0,res=0;
for(int i=0;s[i];i++){
while(j&&s[i]!=p[j])j=f[j];
if(s[i]==p[j])j++;
if(!p[j])res++;
}
return res;
}
void ald(char*t,const char*s,int m){
int l=strlen(s);
if(l<=m)strcat(t,s);
else strncat(t,s,m);
}
void ard(char*t,const char*s,int m){
int l=strlen(s);
if(l<=m)strcat(t,s);
else strcat(t,s+l-m);
}
int main(){char*s1=z1,*s2=z2,*s3=z3;
for(int o_o=1;cin>>n>>p;o_o++){m=strlen(p);
strcpy(s1,"0");strcpy(s2,"1");
f[0]=f[1]=0;
for(int i=1;p[i];i++){
int j=f[i];
while(j&&p[i]!=p[j])j=f[j];
f[i+1]=(p[i]==p[j]?j+1:0);
}
ans[0]=count_p(s1);ans[1]=count_p(s2);
for(int i=2;i<=n;i++){s3[0]=0;
ard(s3,s2,m-1);ald(s3,s1,m-1);
ans[i]=ans[i-1]+ans[i-2]+count_p(s3);
s3[0]=0;
if(strlen(s2)>=m-1&&strlen(s1)>=m-1)
{ald(s3,s2,m-1);ard(s3,s1,m-1);}
else{strcat(s3,s2);strcat(s3,s1);}
char*tmp=s1;s1=s2;s2=s3;s3=tmp;
}
cout<<"Case "<<o_o<<": "<<ans[n]<<endl;
}
return 0;
}