样例输入:
2
样例输出:
2
中文题意:
有一款玩字符串S的有趣的单人游戏。S的长度为n,且只包含字符1
和2
。玩家从字符串的起始位置(index = 0)开始。每一步,玩家要选择从当前位置向左或是向右移动,而移动的距离就是当前位置S[index]的值。例如,当S[index]
= 1
,则这一步结束后,玩家的位置可以是index + 1
或 index - 1
;若S[index]
= 2
,则这一步结束后,玩家的位置可以是index + 2
或 index - 2
。当然,玩家不能移动到字符串外面,也就是说,index总是满足 0≤index
≤n-1。玩家获胜,当且仅当玩家能够访问字符串S中的每个位置刚好1次,并且停在最后一个位置(n-1)。给定n,请计算玩家在最佳情况下可以获胜的字符串数量。因为数量可能非常大,请把它对1000000007取模。
输入格式(Input Specification):
每个输入包含一个测试用例。输入只有一行,是字符串的长度n(1≤n≤2×109)。
输出格式(Output Specification):
在一行中输出答案。
样例提示(Hint):
这两个字符串是11
和12
。
分析:这道题目需要先尝试模拟几个样例,假如我们设长度为n的时候的答案为f[n],我们尝试进行方程的推导。那么f[n]/2代表到达第n个点,不考虑第n个点的值的方案数。那么我们可以从第n-1个点走一步直接到达,那么也就是f[n-1]/2,因为这个时候第n-1个点的取值只能是1,我们从第n-1个点不经过第n-1个点走到第n个点只能走两步,但因为我们没法回去,所以这种情况是不符合题意的。接下来就是从第n-3个点,肯定不能经过一步走到n-2个点,因为我们刚才已经分析出来从第n-2个点是无法直接不经过第n-1个点到达第n个点的,所以这里我们只能走两步到第n-1个点,然后再回退一步到第n-2个点,然后再一次走两步到达第n个点,这样的话方案数也是f[n-3]/2,因为从第n-3到第n-1个点的数已经确定,所以答案就是f[n]/2=f[n-1]/2+f[n-3]/2,那么也就是f[n]=f[n-1]+f[n-3],所以这个过程我们就可以用矩阵快速幂加速递推。递推方程如下:
[fn,fn-1,fn-2]=[fn-1,fn-2,fn-3]*矩阵e
矩阵e为:
1 1 0
0 0 1
1 0 0
那么[fn,fn-1,fn-2]=[f3,f2,f1]*e^(n-3),我们直接用快速幂求出e^(n-3),然后直接一个矩阵乘即可得到结果。
细节见代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=1e5+10,mod=1e9+7;
struct mat{
int a[4][4];
int h,w;
}e,ans;
mat mul(mat a,mat b)
{
mat c;
c.h=a.h;
c.w=b.w;
memset(c.a,0,sizeof (c.a));
for(int k=1;k<=a.w;k++)
for(int i=1;i<=c.h;i++)
for(int j=1;j<=c.w;j++)
c.a[i][j]=(c.a[i][j]+1ll*a.a[i][k]*b.a[k][j])%mod;
return c;
}
int main()
{
int n;
cin>>n;
if(n<=3)
{
printf("2");
return 0;
}
e.w=e.h=3;
e.a[1][1]=e.a[1][2]=e.a[2][3]=e.a[3][1]=1;
ans.w=ans.h=3;
ans.a[1][1]=ans.a[2][2]=ans.a[3][3]=1;
n-=3;
while(n)
{
if(n&1) ans=mul(ans,e);
e=mul(e,e);
n>>=1;
}
mat b;
b.h=1;b.w=3;
b.a[1][1]=b.a[1][2]=b.a[1][3]=2;
b=mul(b,ans);
printf("%d",b.a[1][1]);
return 0;
}