dp加矩阵快速幂,蓝桥杯/2015/初赛(省赛)/C语言A组/9/垒骰子

先关注下斐波那契数列

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;
}

转载于:https://www.cnblogs.com/xsthunder/p/6678089.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值