先关注下斐波那契数列
f(1)=1,f(2)=1,f(n)=f(n-1)+f(n-2),对n,求f(n)
这是一个递推公式,dp的递推公式也是递推公式
O(n)暴力求解
但可以将递推公式转换成矩阵运算再加快速幂来达到O(logn)的加速。
\[ \begin{equation} \begin{bmatrix} f(n) \\ f(n-1) \end{bmatrix} = \begin{bmatrix} 1 & 1\\ 1 & 0 \end{bmatrix} \begin{bmatrix} f(n-1) \\ f(n-2) \end{bmatrix} \end{equation} \]
\[ \begin{equation} \text{因而} \begin{bmatrix} f(n) \\ f(n-1) \end{bmatrix} = \begin{bmatrix} 1 & 1\\ 1 & 0 \end{bmatrix}^{n-2} \begin{bmatrix} 1 \\ 1 \end{bmatrix} \end{equation} \]
\[ \text{其中} \begin{bmatrix} 1 & 1\\ 1 & 0 \end{bmatrix}^{n-2} \text{可以矩阵快速幂来在log(n)时间内计算} \]
可以矩阵快速幂来在log(n)时间内计算
故可以用递推公式转换矩阵,再加矩阵快速幂来计算递推公式,左边一般是列向量,而且甚至可以矩阵(没试过),只要是右边因子是方阵可以乘就可以,这个斐波那契算是强行二维列向量。
这在得到递推公式后,dp,数据大超时的情况下很有效,本题就是这样的一个应用。
简单源于规整先转换下条件
i 和 j 不能相邻 等价于 i 和 j 的对面 不能是相邻两个正面朝上的面。例如,1 和 2 不能相邻表示不能存在相邻两个骰子的正面朝上分别为 1 和 5 ,也不能是 2 和 4 。
\[ \begin{equation} g_{i + 1, j} = sum_{all possible k} g_{i, k}\text{表示用 i 个骰子,最后一层正面朝上的为 j 的方案数} \end{equation} \]
本题,将6维列向量,第i维设为i向上的方案数
f(i)代表那个骰子i向上的方案数
\[ \text{转换} \begin{equation} F{n}= \begin{bmatrix} f(1) \\ f(2) \\ f(3)\\ f(4)\\ f(5)\\ f(6) \end{bmatrix} = A*F{n-1} \end{equation} \text{,A要自己捏,不可能就设零,F(n)代表n个骰子的方案数,第i维设为i向上的方案数,求和就是总方案数。} \]
#include<iostream>
#include<cstring>
using namespace std;
const int MN = 6;
typedef long long M[MN][MN];
long long MOD = 1E9+7;
void MtM(M a,M b,M res){
M ans={{0}};//{{}}只能用于清零//一层一个括号{0},{{0}}
for(int i =0;i<MN;i++){
for(int j =0;j< MN;j++){
ans[i][j]=0;
for(int k =0 ;k<MN;k++){
ans[i][j]+=a[i][k]*b[k][j];
}
ans[i][j]%=MOD;
}
}
memcpy(res,ans,sizeof(ans));//重要的小技巧,可以使得MtM(a,a,a)有效
}
void pM(const M a){
for(int i =0;i< MN;i++){
for(int j =0;j< MN;j++){
cout<<a[i][j]<<' ';
}
cout<<endl;
}
}
void qpM(M a, int n,M ans){
for(int i=0;i<MN;i++){
for(int j =0;j<MN;j++){
ans[i][j]=0;
if(i==j)ans[i][j]=1;
}
}//创建单位元
while(n){//1已经是1次方了,没有0次方,零次方直接输出单位元
if(n&1)MtM(a,ans,ans);
n>>=1;
MtM(a,a,a);
}
}
long long qp(long long a,int n){
long long ans=1;//创建单位元
while(n){//1已经是1次方了,没有0次方,零次方直接输出单位元
if(n&1)ans=a*ans;
n>>=1;
a*=a;
a%=MOD;
ans%=MOD;
}
return ans;
}
int main(){
int n,m;
cin>>n>>m;
M v;
for(int i =0 ;i< MN;i++){
for(int j =0;j<MN;j++){
v[i][j]=1;
}
}
int a,b;
while(m--){
cin>>a>>b;
a--,b--;
v[a][b<3?b+3:b-3]=0;
v[b][a<3?a+3:a-3]=0;
}
M ansM;
long long ans=0;
qpM(v,n-1,ansM);
// pM(ansM);
for(int i =0;i<MN;i++){
for(int j =0;j<MN;j++){
ans+=ansM[i][j];
ans%=MOD;
}
}
cout<<ans<<endl;
ans*=qp(4,n);
ans%=MOD;
cout<<ans<<endl;
}
付快速幂代码
//计算斐波那契
#include<iostream>
#include<cstring>
using namespace std;
const int MN = 2;
typedef int M[MN][MN];
void MtM(M a,M b,M res){
M ans={{0}};//{{}}只能用于清零
for(int i =0;i<MN;i++){
for(int j =0;j< MN;j++){
ans[i][j]=0;
for(int k =0 ;k<MN;k++){
ans[i][j]+=a[i][k]*b[k][j];
}
}
}
memcpy(res,ans,sizeof(ans));//重要的小技巧,可以使得MtM(a,a,a)有效
}
void pM(const M a){
for(int i =0;i< MN;i++){
for(int j =0;j< MN;j++){
cout<<a[i][j]<<' ';
}
cout<<endl;
}
}
void qpM(M a, int n,M ans){
for(int i=0;i<MN;i++){
for(int j =0;j<MN;j++){
ans[i][j]=0;
if(i==j)ans[i][j]=1;
}
}//创建单位元
while(n){//1已经是1次方了,没有0次方,零次方直接输出单位元
if(n&1)MtM(a,ans,ans);
n>>=1;
MtM(a,a,a);
}
}
long long qp(long long a,int n){
long long ans=1;//创建单位元
while(n){//1已经是1次方了,没有0次方,零次方直接输出单位元
if(n&1)ans=a*ans;
n>>=1;
a*=a;
}
return ans;
}
int main(){
ios_base::sync_with_stdio(0);
cin.tie(0);
int n;
cin>>n;
if(n<=2){cout<<1<<endl;
return 0;
}
n-=2;
M ans={{0}};
M v{ 1,1,
1,0 };
qpM(v,n,ans);
pM(ans);
cout<<ans[0][1]+ans[0][0]<<endl;
return 0;
}