【TJOI 2019】甲苯先生的字符串

传送门


problem

给定字符串 s 1 s_1 s1,你想求出满足下列限制的字符串 s 2 s_2 s2 的数量:

  1. s 2 s_2 s2 的长度为 n n n
  2. 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 az。答案对 1 0 9 + 7 10^9+7 109+7 取模。

数据范围: n ≤ 1 0 15 n\le10^{15} n1015


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=az[(uv)=1]f[i1][u]

答案就是 ∑ v = ′ a ′ ′ z ′ f [ n ] [ v ] \sum_{v='a'}^{'z'}f[n][v] v=azf[n][v]

但是 n n n 太大了,直接枚举转移显然是不可行的。这个时候注意到,每一步的转移方程都是一样的

于是,我们就可以从矩阵的角度出发来优化这一转移。也即,把 f [ i − 1 ] f[i-1] f[i1] 看成行向量 A A A ( u v ) (uv) (uv) 构成的矩阵为转移矩阵 B B B,那么 A ∗ B A*B AB 的得到的新的行向量就是 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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值