题目描述:
医学界发现的新病毒因其蔓延速度和Internet上传播的"红色病毒"不相上下,被称为"红色病毒",经研究发现,该病毒及其变种的DNA的一条单链中,胞嘧啶,腺嘧啶均是成对出现的。
现在有一长度为N的字符串,满足一下条件:
(1) 字符串仅由A,B,C,D四个字母组成;
(2) A出现偶数次(也可以不出现);
(3) C出现偶数次(也可以不出现);
计算满足条件的字符串个数.
当N=2时,所有满足条件的字符串有如下6个:BB,BD,DB,DD,AA,CC.
由于这个数据肯能非常庞大,你只要给出最后两位数字即可.
Input
每组输入的第一行是一个整数T,表示测试实例的个数,下面是T行数据,每行一个整数N(1<=N<2^64),当T=0时结束.
Output
对于每个测试实例,输出字符串个数的最后两位,每组输出后跟一个空行.
Sample Input
4
1
4
20
11
3
14
24
6
0
Sample Output
Case 1: 2
Case 2: 72
Case 3: 32
Case 4: 0
Case 1: 56
Case 2: 72
Case 3: 56
如果没有任何条件限制,A、B、C、D组成长度为n的字符串,其个数应该为:4n
因为有了A、C需要出现偶数次的要求,就出现合法和不合法的不同分组。
在不合法的组里,又有
1.A出现奇数次、C出现偶数次;
2.C出现奇数次、A出现偶数次;
3.A出现奇数次、C出现奇数次;
我们用数组
f[n][0]保存长度为n,合法的字符串的个数。
f[n][1]保存长度为n,仅A出现奇数次的字符串的个数。
f[n][2]保存长度为n,仅C出现奇数次的字符串的个数。
f[n][3]保存长度为n,A、C出现奇数次的字符串的个数。
f[n][0]
长度为n-1的合法字符串在末尾加上一个B或者D,都可以变成长度为n的合法字符串。
长度为n-1的仅A出现奇数次的字符串再在末尾加上一个A,也可以变成合法字符串。
长度为n-1的仅C出现奇数次的字符串再在末尾加上一个C,也可以变成合法字符串。
所以,f[n][0] = 2 × f[n-1][0] + f[n-1][1] + f[n-1][2];
f[n][1]
长度为n-1的合法字符串在末尾加上A,都可以变成长度为n的仅A出现奇数次的字符串。
长度为n-1的仅A出现奇数次的字符串再在末尾加上一个B或者D,也可以变成仅A出现奇数次的字符串。
长度为n-1的A、C出现奇数次的字符串再在末尾加上一个C,也可以变成仅A出现奇数次的字符串。
所以,f[n][1] = 2 × f[n-1][1] + f[n-1][0] + f[n-1][3];
f[n][2]
长度为n-1的合法字符串在末尾加上C,都可以变成长度为n的仅C出现奇数次的字符串。
长度为n-1的仅C出现奇数次的字符串再在末尾加上一个B或者D,也可以变成仅C出现奇数次的字符串。
长度为n-1的A、C出现奇数次的字符串再在末尾加上一个A,也可以变成仅C出现奇数次的字符串。
所以,f[n][2] = 2 × f[n-1][2] + f[n-1][0] + f[n-1][3];
f[n][3]
长度为n-1的A、C出现奇数次的字符串在末尾加上一B或者D,都可以变成长度为n的A、C出现奇数次的字符串。
长度为n-1的仅A出现奇数次的字符串再在末尾加上一个C,也可以变成A、C出现奇数次的字符串。
长度为n-1的仅C出现奇数次的字符串再在末尾加上一个A,也可以变成A、C出现奇数次的字符串。
所以,f[n][3] = 2 × f[n-1][3] + f[n-1][1] + f[n-1][2];
综上所述,我们得到:
f[i][0]=f[i−1][0]∗2+f[i−1][1]+f[i−1][2];①
f[i][1]=f[i−1][1]∗2+f[i−1][0]+f[i−1][3];②
f[i][2]=f[i−1][2]∗2+f[i−1][0]+f[i−1][3];③
f[i][3]=f[i−1][3]∗2+f[i−1][1]+f[i−1][2];④
初始状态:
f[0][0] = 2,f[0][1] = 1,f[0][2] = 1,f[0][3] = 0;
推导一次可得:
f[1][0] = 6,f[1][1] = 4,f[1][2] = 4,f[1][3] = 2;
1、第一种方法:矩阵快速幂
一行四列矩阵要得到一行四列矩阵,由性质可得需要四行四列矩阵,
再由①②③④式可得变换矩阵T为
2 1 1 0
1 2 0 1
1 0 2 1
0 1 1 2
即 F[0]*T=F[1],F[2]*T=F[3],F[3]=F[0]*T2,
以此类推可得F[n]=F[0]*Tn-1。
至此可以看出,只需要知道T的n-1次方,即可算出n字符满足条件的个数F[n][0],而T的n-1次方的计算就可以用到矩阵快速幂的算法,模板为:
void quick_mod(ll n) //矩阵快速幂
{
while(n)
{
if(n&1)
{
for(int i=0;i<4;i++)
{
for(int j=0;j<4;j++)
{
ch[i][j]=0;
for(int k=0;k<4;k++)
{
ch[i][j]+=(ans[i][k]*t[k][j])%100;
}
}
}
for(int i=0;i<4;i++)
for(int j=0;j<4;j++)
ans[i][j]=ch[i][j];
n--;
}
n>>=1;
for(int i=0;i<4;i++)
{
for(int j=0;j<4;j++)
{
ch[i][j]=0;
for(int k=0;k<4;k++)
{
ch[i][j]+=(t[i][k]*t[k][j])%100;
}
}
}
for(int i=0;i<4;i++)
for(int j=0;j<4;j++)
t[i][j]=ch[i][j];
}
return ;
}
最后附上AC代码(注意要开long long,为什么?因为不开TLE了 ! T T):
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
ll t[4][4],ans[4][4],ch[4][4];
//t矩阵为转移变换矩阵,ans矩阵为快速幂的结果矩阵,ch矩阵为中间交换矩阵
void init()
{
memset(t,0,sizeof(t));
memset(ans,0,sizeof(ans));
//进行矩阵快速幂后矩阵值改变,每一组测试之前都要初始化
for(int i=0;i<4;i++)
ans[i][i]=1; //初始化单位矩阵
t[0][0]=2,t[1][0]=1,t[2][0]=1,t[3][0]=0;
t[0][1]=1,t[1][1]=2,t[2][1]=0,t[3][1]=1;
t[0][2]=1,t[1][2]=0,t[2][2]=2,t[3][2]=1;
t[0][3]=0,t[1][3]=1,t[2][3]=1,t[3][3]=2;
//构造变换矩阵
return ;
}
void quick_mod(ll n) //矩阵快速幂
{
while(n)
{
if(n&1)
{
for(int i=0;i<4;i++)
{
for(int j=0;j<4;j++)
{
ch[i][j]=0;
for(int k=0;k<4;k++)
{
ch[i][j]+=(ans[i][k]*t[k][j])%100;
}
}
}
for(int i=0;i<4;i++)
for(int j=0;j<4;j++)
ans[i][j]=ch[i][j];
n--;
}
n>>=1;
for(int i=0;i<4;i++)
{
for(int j=0;j<4;j++)
{
ch[i][j]=0;
for(int k=0;k<4;k++)
{
ch[i][j]+=(t[i][k]*t[k][j])%100;
}
}
}
for(int i=0;i<4;i++)
for(int j=0;j<4;j++)
t[i][j]=ch[i][j];
}
return ;
}
int main()
{
while(1)
{
ll t;
cin>>t;
if(t==0)
break;
for(int i=1;i<=t;i++)
{
ll n;
cin>>n;
init(); //矩阵初始化
quick_mod(n-1); //矩阵快速幂
cout<<"Case "<<i<<": "<<(2*ans[0][0]%100+ans[1][0]%100+ans[2][0]%100)%100<<endl;
//最后要求满足条件的个数,所以只需要打印 F(n)[0][0]=F(1)*(T的n-1次方的第一列)
//另外题目要求只输出最后两位,所以要模100
}
cout<<endl; //每组测试数据要进行换行
}
return 0;
}
2、第二种方法:快速幂
变换的四个推导式
f[i][0]=f[i−1][0]∗2+f[i−1][1]+f[i−1][2];①
f[i][1]=f[i−1][1]∗2+f[i−1][0]+f[i−1][3];②
f[i][2]=f[i−1][2]∗2+f[i−1][0]+f[i−1][3];③
f[i][3]=f[i−1][3]∗2+f[i−1][1]+f[i−1][2];④
初始状态:
f[0][0] = 2,f[0][1] = 1,f[0][2] = 1,f[0][3] = 0;
分析四个式子以及初始状态发现:
(1)②③式不同之处为f[i−1][1]∗2与f[i−1][2]∗2,而因为f[0][1] = 1,f[0][2] = 1,所以②③的初始状态相同,所以在经过变化之后结果仍然相同,即f[i][1]=f[i][2]。
(2)①④左右两边相加可得
f[i][0]+f[i][3]=f[i−1][0]∗2+f[i−1][1]*2+f[i−1][2]*2+f[i−1][3]∗2
②③左右两边相加可得
f[i][1]+f[i][2]=f[i−1][0]*2+f[i−1][1]∗2+f[i−1][1]∗2+f[i−1][3]*2
规律为f[i][0]+f[i][3]=f[i][1]+f[i][2],而A、B、C、D组成长度为n的字符串,其个数应该为:4n
∵f[n][0] + f[n][1] + f[n][2] + f[n][3] = 4n
∴f[n][0] + f[n][3] = f[n][1] + f[n][2] = 2 × 4(n-1)
∴f[n-1][1] + f[n-1][2] = 2 × 4(n-2)
∴f[n][0] = 2 × f[n-1][0] + f[n-1][1] + f[n-1][2] = 2 × f[n-1][0] + 2 × 4(n-2)= 2 × f[n-1][0] + 2(2n-3)
继续推导:
f[n][0] = 2 × f[n-1][0] + 2(2n-3)
f[n-1][0] = 2 × f[n-2][0] + 2(2n-5)
┋
f[n-m][0] = 2 × f[n-m-1][0] + 2(2n-2m-3)
┋
f[2][0] = 2 × f[1][0] + 21
f[1][0] = 2
∴f[n][0]
= 2 × f[n-1][0] + 2(2n-3)
=22 × f[n-2][0]+2(2n-4)+2(2n-3)
= 2m × f[n-m][0] + 22(n-m)-1+m-1+ … + 2(2n-3)
= 2n-1 × f[1][0] + 2n-1 + 2n +… + 2(2n-3)
代入f[1][0] = 2
∴f[n][0] = 2n + 2(n-1) + 2n +… + 2(2n-3) = 2(2n-2) + 2(n-1)=4(n-1)+2(n-1)
PS最后一项的解释:第一项乘上初始值即为2n,后面为等比数列求和,第一项为2n-1,末项为2(2n-3),结果=2n+{2(n-1)-2(2n-3)*2}/(1-2)=2×2n-1+2(2n-2)-2(n-1)=2(2n-2)+2(n-1)=4(n-1)+2(n-1)
故要求f[n][0]只需要求4(n-1)+2(n-1)即可,而求ab%k,需要快速幂即可完成,快速幂模板如下:
ll quick_mod(ll a, ll b, ll p)//快速幂:a^b%p
{
ll ans=1;
a%=p;
while(b)
{
if(b&1)
{
ans=ans*a%p;
b--;
}
b>>=1;
a=a*a%p;
}
return ans;
}
如下附AC代码:
#include<iostream>
using namespace std;
typedef long long ll;
ll quick_mod(ll a, ll b, ll p)//快速幂:a^b%p
{
ll ans=1;
a%=p;
while(b)
{
if(b&1)
{
ans=ans*a%p;
b--;
}
b>>=1;
a=a*a%p;
}
return ans;
}
int main()
{
while(1)
{
ll t;
cin>>t;
if(t==0)
break;
for(ll i=1;i<=t;i++)
{
ll n;
cin>>n;
cout<<"Case "<<i<<": "<<(quick_mod(4,n-1,100)+quick_mod(2,n-1,100))%100<<endl;
}
cout<<endl;
}
return 0;
}
参考解析:
https://blog.csdn.net/wongson/article/details/4029863
完结撒花★,°:.☆( ̄▽ ̄)/$:.°★ 。