原文链接https://www.cnblogs.com/zhouzhendong/p/CF117G2.html
题目传送门 - CF177G2
题意
定义斐波那契字符串如下:
$s_1="a"$
$s_2="b"$
$s_i=s_{i-1}+s_{i-2} (igeq 3)$
给定 $k,m$,以及对应的 $m$ 组询问。
每组询问一个字符串 $x$ ,问 $x$ 在 $s_k$ 中出现了多少次。
$kleq 10^{18},mleq 10^4,|x|leq 10^5$
题解
看到 $k$ 如此大,首先要想到矩阵快速幂。
但这个想法暂时还没什么用。
让我们来观察一下字符串的性质。
下面我们分别左对齐和右对齐来看一看斐波那契串。
$$egin{eqnarray*}a\b\ba\bab\babba\babbabab\babbababbabba\babbababbabbababbababend{eqnarray*}$$
$$egin{align*}&a\&b\&ba\&bab\&babba\&babbabab\&babbababbabba\&babbababbabbababbababend{align*}$$
我们可以发现并证明以下性质:
$1.$ 对于任意 $i(i>1)$ ,$s_i$ 为 $s_{i+1}$ 的前缀。
$2.$ 对于任意 $i(i>0)$ ,$s_i$ 为 $s_{i+2}$ 的后缀。
于是:当斐波那契串长度大于询问串的时候,拼接串时在拼接处产生的新的匹配数的变化周期为 $2$ 。
于是,对于长度大于询问串的情况,直接搞两个转移矩阵然后快速幂一下就可以了。
如果长度小于询问串,那么直接回答 $0$ 。
现在再仔细的看看如何求拼接处产生的匹配数。
我们记串 $x$ 在 $s$ 中的出现次数为 $KMP(s,x)$。
则拼接 $s_i,s_{i+1}$ 时,拼接处产生的匹配数为 $KMP(s_{i+1}+s_{i},x)-KMP(s_i,x)-KMP(s_{i+1},x)$ 。
转移矩阵的构造就不说了,比较基础的,请直接看代码。
每次从头开始拼接产生第一个长度比当前询问串大的斐波那契串的复杂度会超时,所以我们需要离线按照询问串长度从小到大来。
这样的复杂度为$O(max{|x|}+mlog m+3^3log k+sum max(|x|,|s_{f(|x|)}|))approx O(mlog m+sum{|x|})$。
代码
#include <bits/stdc++.h> using namespace std; typedef long long LL; const int N=1e6+5,mod=1e9+7; struct Mat{ static const int N=3; int v[N][N]; Mat(){} Mat(int x){ memset(v,0,sizeof v); if (x==1) for (int i=0;i<N;i++) v[i][i]=1; } void set(int p){ v[0][0]=0,v[0][1]=1,v[0][2]=0; v[1][0]=1,v[1][1]=1,v[1][2]=0; v[2][0]=0,v[2][1]=p,v[2][2]=1; } void print(){ for (int i=0;i<3;i++,puts("")) for (int j=0;j<3;j++) printf("%3d ",v[i][j]); puts(""); } Mat operator * (Mat x){ Mat ans(0); for (int i=0;i<N;i++) for (int j=0;j<N;j++) for (int k=0;k<N;k++) ans.v[i][j]=(1LL*v[i][k]*x.v[k][j]+ans.v[i][j])%mod; return ans; } Mat operator ^ (LL y){ Mat x=*this,ans(1); while (y){ if (y&1LL) ans=ans*x; x=x*x,y>>=1; } return ans; } }; LL k; int m; struct STR{ string s; int id,ans; }s[N]; string s1="a",s2="b",s3="ba",s4="bab"; bool cmpL(STR a,STR b){ if (int(a.s.size())==int(b.s.size())) return a.s<b.s; return a.s.size()<b.s.size(); } bool cmpid(STR a,STR b){ return a.id<b.id; } int Fail[N]; char S1[N],S2[N]; int KMP(string &s1,string &s2){ int n=s1.size(),m=s2.size(); for (int i=1;i<=n;i++) S1[i]=s1[i-1]; for (int i=1;i<=m;i++) S2[i]=s2[i-1]; Fail[0]=Fail[1]=0; for (int i=2;i<=m;i++){ int k=Fail[i-1]; while (k&&S2[i]!=S2[k+1]) k=Fail[k]; if (S2[i]==S2[k+1]) k++; Fail[i]=k; } int ans=0,k=0; for (int i=1;i<=n;i++){ while (k&&S1[i]!=S2[k+1]) k=Fail[k]; if (S1[i]==S2[k+1]) k++; if (k==m){ ans++; k=Fail[k]; } } return ans; } int main(){ cin >> k >> m; for (int i=1;i<=m;i++){ cin >> s[i].s; s[i].id=i; } sort(s+1,s+m+1,cmpL); int cnt=1; for (int i=1;i<=m;i++){ while (cnt<k&&int(s1.size())<int(s[i].s.size())){ s1=s4+s3; swap(s1,s2),swap(s2,s3),swap(s3,s4); cnt++; } if (cnt==k){ s[i].ans=KMP(s1,s[i].s); continue; } int a=KMP(s1,s[i].s),b=KMP(s2,s[i].s),c=KMP(s3,s[i].s),d=KMP(s4,s[i].s); int del1=c-a-b,del2=d-b-c; Mat st(0),tn1(0),tn2(0); st.v[0][0]=a,st.v[0][1]=b,st.v[0][2]=1; tn1.set(del1),tn2.set(del2); st=st*((tn1*tn2)^((k-cnt)/2)); if ((k-cnt)&1) st=st*tn1; s[i].ans=st.v[0][0]; } sort(s+1,s+m+1,cmpid); for (int i=1;i<=m;i++) printf("%d ",s[i].ans); return 0; }