problem
给定字符串 s 1 s_1 s1,你想求出满足下列限制的字符串 s 2 s_2 s2 的数量:
- s 2 s_2 s2 的长度为 n n n。
- s 1 s_1 s1 中的相邻的两个字母不能在 s 2 s_2 s2 中相邻地出现。
其中 s 1 s_1 s1 和 s 2 s_2 s2 仅包含小写字母 a ∼ z a \sim z a∼z。答案对 1 0 9 + 7 10^9+7 109+7 取模。
数据范围: n ≤ 1 0 15 n\le10^{15} n≤1015。
solution
我们用 ( u v ) (uv) (uv) 表示字符串 “ u v ” “uv” “uv” 是否是 s 1 s_1 s1 的子串。若是,则 ( u v ) = 0 (uv)=0 (uv)=0,否则 ( u v ) = 1 (uv)=1 (uv)=1。
那么如果用 f [ i ] [ v ] f[i][v] f[i][v] 表示在第 i i i 位填字符 v v v 的方案数,那么有一个比较显然的转移:
f [ i ] [ v ] = ∑ u = ′ a ′ ′ z ′ [ ( u v ) = 1 ] f [ i − 1 ] [ u ] f[i][v]=\sum_{u='a'}^{'z'}[(uv)=1]f[i-1][u] f[i][v]=u=′a′∑′z′[(uv)=1]f[i−1][u]
答案就是 ∑ v = ′ a ′ ′ z ′ f [ n ] [ v ] \sum_{v='a'}^{'z'}f[n][v] ∑v=′a′′z′f[n][v]。
但是 n n n 太大了,直接枚举转移显然是不可行的。这个时候注意到,每一步的转移方程都是一样的。
于是,我们就可以从矩阵的角度出发来优化这一转移。也即,把 f [ i − 1 ] f[i-1] f[i−1] 看成行向量 A A A, ( u v ) (uv) (uv) 构成的矩阵为转移矩阵 B B B,那么 A ∗ B A*B A∗B 的得到的新的行向量就是 f [ i ] f[i] f[i]。这样就可以矩阵快速幂优化了。
时间复杂度 O ( 2 6 3 log n ) O(26^3\log n) O(263logn)。
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define P 1000000007
#define ll long long
using namespace std;
ll n;
char S[100005];
int add(int x,int y) {return x+y>=P?x+y-P:x+y;}
int dec(int x,int y) {return x-y< 0?x-y+P:x-y;}
int mul(int x,int y) {return 1ll*x*y%P;}
struct matrix{
int m[26][26];
matrix(int t=0){
memset(m,0,sizeof(m));
for(int i=0;i<26;++i) m[i][i]=t;
}
friend matrix operator*(const matrix &A,const matrix &B){
matrix C(0);
for(int i=0;i<26;++i)
for(int k=0;k<26;++k)
for(int j=0;j<26;++j)
C.m[i][j]=add(C.m[i][j],mul(A.m[i][k],B.m[k][j]));
return C;
}
friend matrix operator^(matrix A,ll B){
matrix ans(1);
for(;B;B>>=1,A=A*A) if(B&1) ans=ans*A;
return ans;
}
}A,B;
int main(){
scanf("%lld%s",&n,S+1);
int len=strlen(S+1),ans=0;
for(int i=0;i<26;++i) A.m[0][i]=1;
for(int i=0;i<26;++i) for(int j=0;j<26;++j) B.m[i][j]=1;
for(int i=1;i<len;++i) B.m[S[i]-'a'][S[i+1]-'a']=0;
A=A*(B^(n-1));
for(int i=0;i<26;++i) ans=add(ans,A.m[0][i]);
printf("%d\n",ans);
return 0;
}