矩阵快速幂

快速幂

快速幂算法很熟悉很容易理解,原理 nm={(n2)m/2(n2)m/2nmm n m = { ( n 2 ) m / 2 m 为 偶 数 ( n 2 ) m / 2 ⋅ n m 为 奇 数
二分可以将时间复杂度由 O(n) O ( n ) 降到 O(logn) O ( log ⁡ n )
通常m的范围非常大,常常到 109 10 9 ,所以一般给出的题中会要求取模。视情况使用32位或者64位整型。

nm%d n m % d 的实现(见快速幂取模):

int Quick_Power_Mod(int n,int m,int d)
{
    int ans = 1;
    n = n%d;
    while(m > 0)
    {
        if(m%2 == 1)
        {
            ans = (ans * n) % d;
        }
        m = m/2;
        n = (n*n)%d;
    }
    return ans;
}

矩阵快速幂

如果将其中的n换为矩阵的话,则为矩阵快速幂
例题:XDOJ-1027: Feibonaqi数列
1.f(0)=0,f(1)=1
2.f(n)=2f(n-1)+f(n-2),n>1
如果直接递归的话时间会爆,设数组保存的话空间会爆。只能快速幂。
求出递推公式也不可行,因为特征根是无理数。
所以需要找出矩阵形式的递推关系,从而求出矩阵形式的显示公式:
[f(n)f(n1)]= [ f ( n ) f ( n − 1 ) ] = [2110] [ 2 1 1 0 ] ×[f(n1)f(n2)] × [ f ( n − 1 ) f ( n − 2 ) ]

递推得到: [f(n)f(n1)]= [ f ( n ) f ( n − 1 ) ] = [2110]n1 [ 2 1 1 0 ] n − 1 ×[f(1)f(0)] × [ f ( 1 ) f ( 0 ) ]

则使用快速幂即可解决:

//XDOJ-1027----201.3.9
//矩阵快速幂 
#include<iostream>
#include<cstdlib>
typedef long long LL;
const long long Max=1000000007;
using namespace std;
void Matrix_mul(LL A[2][2],LL B[2][2]); //矩阵相乘 
LL Matrix_pow_mod(int n);    

int main(void)
{
    int n;
    while(cin >> n)
    {
        if(n == 0 || n == 1)
        {
            cout << n << "\n";
        }
        else
        {
            cout << Matrix_pow_mod(n-1) << "\n";
        }
    }
    return 0;
}
//2*2矩阵想乘 
void Matrix_mul(LL A[2][2],LL B[2][2])
{
    LL ans[2][2] = {{0,0},{0,0}};
    for(int i = 0; i < 2 ; i++)
    {
        for(int j= 0; j < 2; j++)
        {
            for(int k=0; k < 2; k++)
            {
                ans[i][j] += A[i][k] * B[k][j];
            }
            if(ans[i][j] > Max) //大于时才取模 
            {
                ans[i][j] %= Max;
            }
        }
    }
    for(int i=0; i < 2; i++)
    {
        for(int j=0; j < 2; j++)
        {
            B[i][j] = ans[i][j];
        }   
    }   
}

//矩阵快速幂 
LL Matrix_pow_mod(int n)
{
    LL A[2][2] = {{2,1},{1,0}};
    LL Ans[2][2] = {{1,0},{0,1}};   //二阶单位阵
    while(n > 0)
    {
        if(n%2 == 1) // n & 1
        {
            Matrix_mul(A,Ans);
        }
        Matrix_mul(A,A);
        n = n/2; //n >>= 2;
    }
    return Ans[0][0]; 
} 

类似题:poj-3070-Fibonacci

构造递推关系

上面的递推关系很简单,这类问题的重点就是构造矩阵递推关系。

如:POJ 3233 Matrix Power Series

考虑到 Sk=ki=1Ai=ESk1+Ak S k = ∑ i = 1 k A i = E ⋅ S k − 1 + A k 或者 Sk=ASk1+A S k = A ⋅ S k − 1 + A
可以构造出:
[SkAk]= [ S k A k ] = [E0AA] [ E A 0 A ] ×[Sk1Ak1] × [ S k − 1 A k − 1 ] 或者 [SkE]= [ S k E ] = [A0AE] [ A A 0 E ] ×[Sk1E] × [ S k − 1 E ]

方法不一。其中A是n阶方阵,E是n阶单位阵,0是n阶零矩阵。

比如由前者就可以得出: [SkAk]= [ S k A k ] = [E0AA]n1 [ E A 0 A ] n − 1 ×[AA] × [ A A ] 或者 [E0AA]n [ E A 0 A ] n =[E0SkAk] = [ E S k 0 A k ]

实现:

//poj-3233----矩阵快速幂
//2018,3,10
#include<iostream>
#include<cstring>
using namespace std;
const int N = 65;

void Matrix_mul(int A[N][N],int B[N][N],int n,int m);
void Matrix_pow_mod(int n,int k,int m,int A[N][N]);
void print_matrix(int A[N][N],int n);
void input_matrix(int A[N][N],int n);

int main(void)
{
    int n,k,m;
    int A[N][N];
    while(cin >> n >> k >> m)
    {
        input_matrix(A,n);
        Matrix_pow_mod(n,k,m,A);
    }
    return 0;
}

void Matrix_mul(int A[N][N],int B[N][N],int n,int m)
{
    int res[N][N];
    memset(res,0,sizeof(res));
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 1; j <= n; j ++)
        {
            for(int k = 1; k <= n; k ++)
            {
                if(A[i][k] || B[k][j])
                {
                    res[i][j] += A[i][k] * B[k][j];
                    res[i][j] %= m;
                }
            }   
        }
    }
    for(int i = 0; i <= n; i ++)
    {
        for(int j = 0; j <= n; j ++)
        {
            B[i][j] = res[i][j];
        }
    }
}

void Matrix_pow_mod(int n,int k,int m,int A[N][N])
{
    int B[N][N];
    int ans[N][N];
    int C[N][N];
    //矩阵初始化 
    memset(B,0,sizeof(B));
    memset(ans,0,sizeof(ans));
    memset(C,0,sizeof(C));
    for(int i = 1; i <= n; i ++)
    {
        B[i][i] = 1;
    }
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 1; j <= n; j ++)
        {
            B[i][n+j] = A[i][j];
            B[n+i][n+j] = A[i][j];
        }
    }
    for(int i=0; i <= 2*n; i++)
    {
        ans[i][i] = 1;
    }
    //求k次幂 
    while(k > 0)
    {
        if(k & 1)
        {
            Matrix_mul(B,ans,2*n,m);
        }
        k >>= 1;
        Matrix_mul(B,B,2*n,m);
    }
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 1; j <= n; j ++)
        {
            C[i][j] = ans[i][n+j];
        }
    }
    //print the result 
    print_matrix(C,n);
}

void print_matrix(int A[N][N],int n)
{
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 1; j < n; j ++)
        {
            cout << A[i][j] << " ";
        }
        cout << A[i][n] << endl;
    }
}

void input_matrix(int A[N][N],int n)
{
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 1; j <= n; j ++)
        {
            cin >> A[i][j];
        }
    }
}

优化:很明显可以看到,2n阶方阵中左半部分在乘幂中是没有变化的,所以可以优化掉大概一半的时间(这里没有实现)。一般情况下对于其中0较多的矩阵,可以先判断是否为0再乘。

注意这里的矩阵乘法采用的是一般的算法,复杂度 O(n3) O ( n 3 ) ,矩阵乘法有一个 O(nlog7) O ( n l o g 7 ) 的算法: Strassen算法。采用分治策略,比较复杂暂时不会不做实现。

综上:矩阵快速幂思路与快速幂完全一致,重点是找到递推关系

对于一些简单的递推式:

1. f(n)=af(n1)+bf(n2)+c f ( n ) = a f ( n − 1 ) + b f ( n − 2 ) + c

f(n)f(n1)c= ( f ( n ) f ( n − 1 ) c ) = a10b00101 ( a b 1 1 0 0 0 0 1 ) ×f(n1)f(n2)c × ( f ( n − 1 ) f ( n − 2 ) c )

2. f(n)=acn+bf(n1)+d f ( n ) = a c n + b f ( n − 1 ) + d

f(n)cnd= ( f ( n ) c n d ) = b00acc0101 ( b a c 1 0 c 0 0 0 1 ) ×f(n1)cn1d × ( f ( n − 1 ) c n − 1 d )

类似简单矩阵快速幂模板题:
HOJ 175HOJ 1575

不算那么简单的递推+矩阵快速幂:HOJ 3483 - A Very Simple Problem

题意:求

(k=1Nkxxk)modM ( ∑ k = 1 N k x ⋅ x k ) mod M

数据范围: 1N,M2109,and1x50 1 ≤ N , M ≤ 2 ∗ 10 9 , a n d 1 ≤ x ≤ 50

建立递推关系: Sn+1=Sn+(n+1)xxn+1 S n + 1 = S n + ( n + 1 ) x ⋅ x n + 1
(n+1)x ( n + 1 ) x 二项式展开为 xk=0C(x,k)nk ∑ k = 0 x C ( x , k ) n k ,则可以构造出如下矩阵递推式,然后使用矩阵快速幂。

|1 xC(x,0) xC(x,1) xC(x,2) ... xC(x,x)|  |Sn       | |S(n+1)           | 
|0 xC(0,0) 0       0       ... 0      |  |x^n * n^0| |x^(n+1) * (n+1)^0| 
|0 xC(1,0) xC(1,1) 0       ... 0      | *|x^n * n^1|=|x^(n+1) * (n+1)^1| 
|0 xC(2,0) xC(2,1) xC(2,2) ... 0      |  |x^n * n^2| |x^(n+1) * (n+1)^2| 
|...                                  |  |...      | |...              | 
|0 xC(x,0) xC(x,1) xC(x,2) ... xC(x,x)|  |x^n * n^x| |x^(n+1) * (n+1)^x|
//还能这样构造,我是真的佩服,只能说我太菜吧!
//出处:http://blog.csdn.net/winycg/article/details/52982723

实现:

//hoj--3483--矩阵快速幂 + 二项式定理 
//2018.3.12
#include<iostream>
#include<cstring>
using namespace std;
typedef long long LL;
const int N = 55;

struct Matrix
{
    LL mat[N][N];   
};

Matrix Matrix_mul(Matrix A,Matrix B, int n,int m); 
Matrix Matrix_pow_mod(Matrix A,int n,int k,int m);
LL C[N][N];
void Combination_num();//use Pascal identity to canculate Combination number

int main(void)
{
    Combination_num();
    int n,x,m;
    while(cin >> n >> x >> m && n != -1)
    {
        Matrix S;
        memset(S.mat,0,sizeof(S.mat));
        S.mat[1][1] = 1;
        for(int i = 2; i <= x+2; i ++)
        {
            S.mat[1][i] = (x * C[x][i-2]) % m;
        }
        for(int i = 2; i <= x+2; i ++)
        {
            for(int j = 2; j <= i; j ++)
            {
                S.mat[i][j] = (x * C[i-2][j-2]) % m;
            }
        }
        LL sum = 0;
        Matrix ans = Matrix_pow_mod(S,x+2,n-1,m);
        for(int i = 1; i <= x+2; i ++)
        {
            sum = (sum + x * ans.mat[1][i]) % m;
        }
        cout << sum << endl;
    }


    return 0;
}


Matrix Matrix_mul(Matrix A,Matrix B, int n,int m)
{
    Matrix res;
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 1; j <= n; j ++)
        {
            res.mat[i][j] = 0;//init the matrix 
            for(int k = 1; k <= n; k ++)
            {
                res.mat[i][j] = (res.mat[i][j] + A.mat[i][k] * B.mat[k][j]) % m;
            }
        }
    }
    return res;
}

Matrix Matrix_pow_mod(Matrix A,int n,int k,int m)
{
    Matrix res;
    //initialization 
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 1; j <= n; j ++)
        {
            res.mat[i][j] = 0;
        }
        res.mat[i][i] = 1;
    }
    while(k > 0)
    {
        if(k & 1)
        {
            res = Matrix_mul(A,res,n,m);
        }
        k >>= 1;
        A = Matrix_mul(A,A,n,m);
    }
    return res;
}

void Combination_num()
{
    C[0][0] = C[1][0] = C[1][1] = 1;
    for(int i = 2; i < N; i ++)
    {
        C[i][0] = C[i][i] = 1;
        for(int j = 1; j < i; j ++)
            C[i][j] = C[i-1][j] + C[i-1][j-1];//of course it's Pascal identity
    }
}

其中组合数使用帕斯卡恒等式(终于用上了)进行递推,运算次数很少。
矩阵用结构体封装,写起来很舒服,Matrix_mulMatrix_pow_mod两个函数返回结构,效率不高,可以用引用参数代替。

类似题:HOJ 2855HOJ 3658HOJ 4565


参考链接:http://blog.csdn.net/wust_zzwh/article/details/52058209

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值