zoukankan      html  css  js  c++  java
  • 整数快速乘法/快速幂+矩阵快速幂+Strassen算法 (转)

    整数快速乘法/快速幂+矩阵快速幂+Strassen算法

     

    快速幂算法可以说是ACM一类竞赛中必不可少,并且也是非常基础的一类算法,鉴于我一直学的比较零散,所以今天用这个帖子总结一下

    快速乘法通常有两类应用:一、整数的运算,计算(a*b) mod c  二、矩阵快速乘法

    一、整数运算:(快速乘法、快速幂)

    先说明一下基本的数学常识:

    (a*b) mod c == ( (a mod c) * (b mod c) ) mod c //这最后一个mod c 是为了保证结果不超过c

    对于2进制,2n可用1后接n个0来表示、对于8进制,可用公式 i+3*j == n (其中 0<= i <=2 ),对于16进制,可用 i+4*j==n(0 <= i <=3)来推算,表达形式为2i 后接 j 个0。

    接下来让我们尽可能简单的描述快速乘法的思想:

    a*b

    快速乘法的基本思想 ,是二进制和乘法分配律的结合,(不由得想起浮点数不满足结合律,严重吐槽!!!╮(╯-╰)╭),比如说,13 ==(1101)2  ,4*13等于4*(1101)2 ,用分配律展开得到4*13 == 4*(1000+100+1)2,我们不难观察出,快速乘法可以通过判断当前的位(bit)是1还是0,推断出是否需要做求和操作,每次移动到下一位(bit)时,就对ans进行*2操作,等待是否求和。由于除以2和位移操作是等效的,因此这也可以看作是二分思想的应用,这种算法将b进行二分从而减少了不必要的运算,时间复杂度是log(n)。

    a^b

    快速幂其实可以看作是快速乘法的特例,在快速幂中,我们不再对ans进行*2操作,因为在a^b中b的意义已经从乘数变成了指数,但是我们可以仍然把b写成二进制,举例说明:此时,我们将4*13改为4^13,13=(1101)2 ,二进制13写开我们得到(1000+100+1),注意,这里的所有二进制是指数,指数的相加意味着底数相乘,因此有4^13 == 48 * 44 * 41。再注意到指数之间的2倍关系,我们就可以用很少的几个变量,完成这一算法。这样,我们就将原本用循环需要O(n)的算法,改进为O(logN)的算法。

    按照惯例,给出尽可能简洁高效的代码实现 (以下所有int都可用long long 代替)

    首先,给出快速乘法的实现:

     
    1 //快速乘法 
    2 int qmul(int a,int b){// 根据数据范围可选择long long 
    3     int ans=0;
    4     while(b){
    5         if( b&1)ans+=a;//按位与完成位数为1的判断
    6         b>>=1;a<<=1;//位运算代替/2和*2
    7     }
    8     return ans;
    9 }

    如果涉及到快速乘法取模,则需要进行一些微小改动

    改动所基于的数学原理,请参考红色字体标出的数学常识

     
    1 //快速乘法取模 
    2 int qmul_mod(int a,int b,int mod){
    3     int ans=0;
    4     while(b){
    5         if((b%=mod)&1)ans+=a%=mod;//这里需要b%=mod 以及a%=mod 
    6         b>>=1;a<<=1;
    7     }
    8     return ans%mod;  //ans也需要对mod取模 
    9 }

    接下来是快速幂的实现:

     
     1 //快速幂 a^b 
     2 int qpow(int a,int b){
     3     if(a==0)return 0;//这是个坑,校赛被坑过,很多网上的实现都没写这一点
     4     int ans=1;
     5     while(b){
     6         if(b&1)ans*=a;//和快速乘法的区别
     7         b>>=1;a*=a;//区别,同上
     8     }
     9     return ans;
    10 } 
     

    以及含有取模的快速幂:

     
    int qpow_mod(int a,int b,int mod){
        if(a==0)return 0;
        int ans=1;
        while(b){
            if(b&1)ans=(ans%mod)*(a%mod);//如果确定数据不会爆的话,可写成 ans*=a%=mod;
            b>>=1;a*=a%=mod;//等价于a=(a%mod)*(a%mod),且将一个模运算通过赋值代替,提高了效率
        }
        return ans%mod;//数据不会爆的话,这里的%运算会等价于第5中不断重复的 ans%mod
    }

    如果我们对于性能还有更进一步的要求,那么也就是减少取模运算了,那么我们需要确定数据范围不会爆掉

    在这样的前提下,我们可以只用原先1/4的取模运算量完成快速幂

     
    int qpow_mod(int a,int b,int mod){
        if(!a)return 0;
        int ans=1;
        while(b){
            if(b&1)ans*=a%=mod;//这里的模运算只有一个
            b>>=1;a*=a;//这里的模运算没有了
        }
        return ans%mod;
    }

    这些天找了好久,终于找到了纯粹的整数快速幂题目,按照惯例,给一波传送门:

    poj1995:http://poj.org/problem?id=1995

    这个题。。。没什么好说的,但是需要注意,用1/4模运算量的那种写法,数据会爆,所以必须写成完全取模的运算,这样程序会慢一点。。。呜呜呜,63ms水过,这是目前我做的最慢的了,如果大神知道如何在16ms及以下A掉它,欢迎联系我谢谢~o(* ̄▽ ̄*)ブ

    实现的代码如下:

     1 #include<cstdio>
     2 int z,a,b,m,h,sum;
     3 int qpow_mod(int a,int b,int mod){
     4     if(!a)return 0;
     5     int ans=1;
     6     while(b){
     7         if(b&1)ans=ans%mod*(a%=mod);
     8         b>>=1;a=a%mod*(a%mod);
     9     }
    10     return ans%mod;
    11 }
    12 int main(){
    13     scanf("%d",&z);
    14     while(z--){
    15         scanf("%d%d",&m,&h);sum=0;
    16         while(h--){
    17             scanf("%d%d",&a,&b);
    18             sum+=qpow_mod(a,b,m);
    19             sum%=m;
    20         }
    21         printf("%d
    ",sum);
    22     }
    23 }
    View Code

    先更新到这,有时间再更新矩阵的Strassen算法以及矩阵快速幂,,大家稍后见(●'◡'●)

    2016-06-13 16:47:56

    大家好,我又回来啦

    二、矩阵运算:(快速幂)(Strassen算法有空再说)

    矩阵的快速幂运算,其实思路和上面的整数快速幂是一样的,对指数进行二分,不过我们对于快速幂本身,可能既可以写成函数,也可以写成运算符重载,所以这里我写的是运算符的重载,毕竟重载练得少,得多练一练

    首先我们可以定义一个矩阵数据结构,也可以直接用二维数组

    1 #define N 100
    2 struct matrix{
    3     int m[N][N];
    4 };

    然后我们重载^运算符,完成矩阵m的b次幂的快速幂运算

    这里为了我自己代码习惯,我重载了*和*=两种运算符,当然,在写的时候跪在了忘了写函数声明上,毕竟C++不是java,对于函数声明的顺序有依赖,so~大家记得写函数声明呦

    代码如下

     
     1 //矩阵的数据结构
     2 struct matrix{
     3     int m[N][N];
     4 };
     5 matrix operator * (matrix ,matrix);//重载声明
     6 matrix operator *= (matrix,matrix);
     7 matrix operator ^ (matrix a,int b){
     8     matrix ans;
     9     for(int i=0;i<N;i++)
    10         for(int j=0;j<N;j++)ans.m[i][j]=(i==j);//初始化为单位矩阵,矩阵A*A的单位矩阵=A
    11     if(b&1)ans*=a;
    12     b>>=1;a*=a;
    13     return ans;
    14 }
    15 matrix operator * (const matrix a,const matrix b){//朴素矩阵乘法
    16     matrix ans;
    17     for(int i=0;i<N;i++)
    18         for(int j=0;j<N;j++)
    19             for(int k=0;k<N;k++)
    20                 ans.m[i][j]=a.m[i][k]+b.m[k][j];
    21     return ans;
    22 }
    23 matrix operator *= (matrix a,const matrix b){
    24     return a=b*b;//此处应为a=a*b;
    25 }

    当然,有必要的时候,我会再更新一波Strassen算法,考虑到在很多情况下,Strassen算法反而会降低矩阵运算的速度,所以我们就先到这里~拜拜(●ˇ∀ˇ●)

    应用篇

    主要通过把数放到矩阵的不同位置,然后把普通递推式变成"矩阵的等比数列",最后快速幂求解递推式:

    线性递推方程即形如fn=a1fn-1+a2fn-2+……+aifn-i的方程

    先通过入门的题目来讲应用矩阵快速幂的套路(会这题的也可以看一下套路):

    例一:http://poj.org/problem?id=3070
    题目:斐波那契数列f(n),给一个n,求f(n)%10000,n<=1e9;

    (这题是可以用fibo的循环节去做的,不过这里不讲,反正是水题)

    矩阵快速幂是用来求解递推式的,所以第一步先要列出递推式:

     f(n)=f(n-1)+f(n-2)

    第二步是建立矩阵递推式,找到转移矩阵:

    ,简写成T * A(n-1)=A(n),T矩阵就是那个2*2的常数矩阵,而

    这里就是个矩阵乘法等式左边:1*f(n-1)+1*f(n-2)=f(n);1*f(n-1)+0*f(n-2)=f(n-1);

    这里还是说一下构建矩阵递推的大致套路,一般An与A(n-1)都是按照原始递推式来构建的,当然可以先猜一个An,主要是利用矩阵乘法凑出矩阵T,第一行一般就是递推式,后面的行就是不需要的项就让与其的相乘系数为0。矩阵T就叫做转移矩阵(一定要是常数矩阵),它能把A(n-1)转移到A(n);然后这就是个等比数列,直接写出通项:此处A1叫初始矩阵。所以用一下矩阵快速幂然后乘上初始矩阵就能得到An,这里An就两个元素(两个位置),根据自己设置的A(n)对应位置就是对应的值,按照上面矩阵快速幂写法,res[1][1]=f(n)就是我们要求的。

     另种解释:在斐波那契数列之中

    f[i] = 1*f[i-1]+1*f[i-2]  f[i-1] = 1*f[i-1] + 0*f[i-2];

    所以

    就这两幅图完美诠释了斐波那契数列如何用矩阵来实现。

    给一些简单的递推式
    1.f(n)=a*f(n-1)+b*f(n-2)+c;(a,b,c是常数)


    2.f(n)=c^n-f(n-1) ;(c是常数)


    继续例题二:poj3233

    Description

    Given a n × n matrix A and a positive integer k, find the sum S = A + A2 + A3 + … + Ak.

    Input

    The input contains exactly one test case. The first line of input contains three positive integers n (n ≤ 30), k (k ≤ 109) and m (m < 104). Then follow n lines each containing n nonnegative integers below 32,768, giving A’s elements in row-major order.

    Output

    Output the elements of S modulo m in the same way as A is given

    这题就是求一个矩阵的和式:S(k),直接对和式建立递推:


    建立矩阵,注意此处S和A都是2*2的矩阵,E表示单位矩阵,O表示零矩阵(全是0,与其他矩阵相乘都为0),显然E,O都是2*2的

    这里转移矩阵是4*4.OVER

     

    一般这种题目都是要找递推式,这是难点.

    例三:HDU2276

    题意就是说n个灯排成环i号灯的左边是i-1号,1的左边是n号,给定初始灯的开闭状态(用1,0表示),然后每一秒都操作都是对于每个灯i,如果它左边的灯是开的就把i状态反转(0变1,1变0),问m秒都最终状态是什么,m<=1e8,n<=100;

    这题的递推式没有明说,但是每一秒操作一次其实就是一次递推,那么关键就是要找转移矩阵,而且比较是常数矩阵,这个才能用等比的性质

    我们把n个灯的状态看出一个F(n),其实就是一个n*1的01矩阵,然后考虑从上一秒的状态F(n-1)如何转移到F(n)。既然每个状态都要转移,总共n个状态,可以看出转移矩阵就是一个n*n的矩阵(每一个灯的状态都用一个1*n的矩阵来转移)

    先考虑第一个灯:影响它新状态的只有它左右的灯和它自己的状态,仔细想一下,1*n的转移中只有这两位置有用,那么其他都是0,就这样

    这里state2到staten-1都被0干掉了,只有第一个和1号左边的灯有效

    (这里不具体讲了,只有0,1的状态,自己枚举一下state1和state2,矩阵相乘后都是能正确转移的)

    其他灯的考虑都是一样的,最终:

    OVER

    例四:HDU 5015
    题意就是一个矩阵a,第一行依次是0,233,2333,23333......,给出矩阵的递推式:a[i][j]=a[i-1][j]+a[i][j-1],输入的是第零列,求a[n][m],n ≤ 10,m ≤ 109
    解:n很小,m很大,m大概就是作为幂次,以每一列为一个矩阵A(n-1)并且向下一列A(n)转移,那么关键就是找转移矩阵了:
    先看第一行的数233后面每次都添一个3,显然递推关系就是A(n-1)*10+3=A(n),这里多一个3,必须把列数新增一个元素3,也就是一个(n+1)*1的矩阵A(n).
    然后其他的元素转移会出现一个问题,a[i][j]=a[i-1][j]+a[i][j-1];这里a[i-1][j]依旧是新一列的元素,这是不能矩阵转移的,因为矩阵转移必须从旧的矩阵状态A(n-1)变到新状态A(n)。
    那么这里a[i-1][j]就用递归思想,沿用上一行的转移矩阵(上一行能转移出a[i-1][j]),再加上新增的转移:

    OVER

    当然矩阵还有很多奇葩的递推,比如这矩阵优化的dp题。HDU5318

    推荐一些题目:

    简单的:

    http://acm.hdu.edu.cn/showproblem.php?pid=1757

    http://acm.hdu.edu.cn/showproblem.php?pid=1575

    不简单的:

    http://acm.hdu.edu.cn/showproblem.php?pid=3483

    http://acm.hdu.edu.cn/showproblem.php?pid=2855

    http://acm.hdu.edu.cn/showproblem.php?pid=3658

    http://acm.hdu.edu.cn/showproblem.php?pid=4565

     另一篇矩阵二分快速幂入门与进阶

    矩阵快速幂——入门

    题目链接POJ307

    题意:求第n位斐波那契数mod 10000的大小。其中n的大小高达1000000000

    由于我对矩阵快速幂也不是很了解,所以只能浅谈,不敢深谈,希望对新手有点帮助。

    先聊聊快速幂

    首先,如果要你自己写一个pow()函数,需要求220,怎么求

    #include <iostream>
    using namespace std;
    int quickPow(int a,int b){   //快速幂
        int ans = 1;
        while(b){
            if(b&1) ans*=a;
            a *= a;
            b /= 2;
        }
        return ans;
    }
    int pow(int a,int b){   //普通乘
        int ans = 1;
        for(int i=1; i<=b; i++)
            ans *= a;
        return ans;
    }
    int main(int argc, const char * argv[]) {
        cout<<quickPow(2, 20)<<endl;   //第一种方式
        cout<<pow(2,20)<<endl;    //第二种方式
        return 0;
    }

    上面的两种方式,一种复杂度是O(logn),一种复杂度是O(n)。当你数量级很大的时候,比如10000000000时,这个复杂度的时间差别就很大了

    怎么得到这个快速幂的代码的呢?我们假设需要求274

    74转化为二进制为1001010,根据二进制对应位的1,可以把74拆分为64+8+2 ,所以
    74 = 26 + 23 + 21
    274 = 226 + 223 + 221

     while(b){    //快速幂核心代码
          if(b&1) ans*=a;
          a *= a;
          b /= 2;
     }

    下面看一下上面这段快速幂程序中的ans和a的用法 (74二进制1001010为从右到左下面表格从1-7)

     初始值01(ans变了)01(ans变了)001(ans变了)
    a = 2n 21 22 24 28 216 232 264 2128
    ans 20 20 20×22 20×22 20×22×28 20×22×28 20×22×28 20×22×28×264

    这里有两个变量,第一个变量a默认用来存2n,,变量ans存储的是最后的结果,当遇到第k位的二进制位是1的时候,则ans ×= 2k

    举个例子,74的二进制表示是1001010,因为是除2运算,所以从右往左遍历,到右边第二个时,ans ×= 22,到右边第四个时,ans ×= 28,到右边第7个时,ans ×= 264,最后ans = 274,至于ans乘的过程中的那些22、28、264怎么来?a变量不是一直在不断增加吗,ans乘的就是a变量对应位的值,对应上面的表格可能看的更清楚一点

    不知道,你看懂没……如果还是没懂,就给个面子装懂好嘛……

    矩阵?怎么过渡到斐波那契

    • 宏观上看矩阵

     

    上图中,乘号右边是一系列的矩阵。我们称为B1、B2、B3、B4 …… Bn
    该序列中的同行为一个斐波那契数列。乘号左边是一个转移矩阵A,Bn = A×Bn-1

    • 微观上看矩阵

       

    原始的序列是 [A,B],需要得到的序列是[A+B,A],根据矩阵相乘定律,可以得到转移矩阵A

    • 总结一波
      现在需要求的是Bn%10000,Bn = B1 × An,B1[1,1],所以 An就是需要的答案。那把求Bn改成了求An有什么好处呢?见下文

    矩阵快速幂的实际运用

    上述矩阵代替斐波那契数列的方式。已经把斐波那契数列从原先的递加变成了现在的递乘。那变成递乘有什么用?

    如果斐波那契第一项是A,第二项是A2,第三项是A3,第4项是A4 ……
    那第8项的值是,A8 = A4×A4

    换句话说,第4项的值 × 第4项的值 = 第8项的值

    求第16项的值
    原来的方式是:1->2->3->4……->15->16需要经过15次运算
    现在的方式是:1->2->4->8->16只需要经过4次运算

    为什么只需要经过4次?从A->A2得到第2项,我要求第4项不需要经过第3项,直接A2 × A2 -> A4就可以得到第4项。这又回到了刚开始的快速幂

    具体怎么得到,下面请看代码解析

    代码拆分

    代码不长,先看下面这个函数。这里使用了引用传递,即a数组发生变化后,传入的实参也会改变。例如传入数组a数组b,得到的数组a = 数组a × 数组b。即Matrix(cot,temp)执行的结果是cot *= temp,且cottemp都表示的是矩阵数组

    void Matrix(int (&a)[2][2],int b[2][2]){   //矩阵乘法
        int tmp[2][2] = {0};
        for(int i = 0; i < 2; ++i)
            for(int j = 0; j < 2; ++j)
                for(int k = 0; k < 2; ++k)
                    tmp[i][j] = (tmp[i][j] + a[i][k] * b[k][j]) % N;  //矩阵相乘,注意mod
        for(int i = 0; i < 2; ++i)
            for(int j = 0; j < 2; ++j)  
                a[i][j] = tmp[i][j]; //赋值返回数据
    }

    A1,也就是转移矩阵,题中已经给出了,用数组表示是[1,1,1,0]。这里还需要一个A0,A0其实就是单位矩阵E(E × 任意矩阵A = A)。单位矩阵用数组表示就是[1,0,0,1](对角线都是1的矩阵)

    while(n){
           if(n & 1) Matrix(cot,temp);   //如果是奇数
           Matrix(temp,temp);   
           n /= 2;  //不断除2
    }

    上述5行代码是矩阵快速幂关键(至于怎么得到这5行代码的,在上述快速幂的解释中应该已经可以得到了)。cot数组一开始是A0temp数组一开始是A1。我们来模拟下求第13项的斐波那契值是怎么求的。

    第一步:n = 13,执行Matrix(cot,temp)Matrix(temp,temp),得到cot数组为A1t数组为A2n = 6

    第二步:n = 6,执行Matrix(temp,temp),得到cot数组为A1t数组为A4n = 3

    第三步:n = 3,执行Matrix(cot,temp)Matrix(temp,temp),得到cot数组为A5t数组为A8n = 1

    第四步:n = 1,执行Matrix(cot,temp)Matrix(temp,temp),得到cot数组为A13t数组为A16n = 0

    退出while循环并输出cot数组对应的值。一共进行了4步

    完整代码

    #include <iostream>
    using namespace std;
    int N = 10000,n;
    void Matrix(int (&a)[2][2],int b[2][2]){
        int tmp[2][2] = {0};
        for(int i = 0; i < 2; ++i)
            for(int j = 0; j < 2; ++j)
                for(int k = 0; k < 2; ++k)
                    tmp[i][j] = (tmp[i][j] + a[i][k] * b[k][j]) % N;
        for(int i = 0; i < 2; ++i)
            for(int j = 0; j < 2; ++j)
                a[i][j] = tmp[i][j];
    }
    int main(){
        while(cin>>n && n!=-1){
            int temp[2][2] = {1, 1, 1, 0},cot[2][2] = {1, 0, 0, 1};
            while(n){
                if(n & 1) Matrix(cot,temp);
                Matrix(temp,temp);
                n /= 2;
            }
            cout<<cot[0][1]<<endl;
        }
        return 0;
    }

    这篇文章主要利用斐波那契数列来浅谈矩阵快速幂的用法,矩阵快速幂还有更大的使用空间,后续有空会补上矩阵快速幂的其他用法,另外送上一篇文章 十个利用矩阵乘法解决的经典题目



    矩阵快速幂——进阶

    上一篇文章讲解了下基于斐波那契数列的矩阵快速幂,即F(n) = F(n-1) + F(n-2),转移矩阵比较简单。这里介绍一个比较复杂的,递推公式为非线性的例子,着重讲一下求解转移矩阵和应用转移矩阵的过程

    准备问题

    讲算法肯定要有相应的OJ,这里附上一道题,HDU5950

    简单讲解下题意。F(n) = F(n-1) + 2F(n-2) + n4,且F(1) = a , F(2) = b,求F(n)%2147493647,其中a、b、n是待输入的参数

    我们把解决这类题目的过程分解为如下步骤:

    1. 把非线性递推式转化为线性递推式(线性递推式可忽略第一步)
    2. 根据线性递推式得到F(n)和F(n+1)的矩阵序列
    3. 根据F(n)和F(n+1)的矩阵序列得到中间的转移矩阵
    4. 根据转移矩阵编写代码

    分步拆解问题

    首先把非线性递推式转换为线性递推式
    ∵ (n+1)4 = n4 + 4n3 + 6n2 + 4n + n0
    ∵ F(n+1) = F(n) + 2F(n-1) + (n+1)4
    ∴ F(n+1) = F(n) + 2F(n-1) + n4 + 4n3 + 6n2 + 4n + n0

    然后我们看下下面的状态转移矩阵


    图(1)

    图(1)左边的矩阵表示F(n)项的矩阵,右边的矩阵表示F(n+1)项的矩阵。而中间的A矩阵就是需要求的转移矩阵。图(1)左边的矩阵怎么得到?我们看刚才得到的递归式

    F(n+1) = F(n) + 2F(n-1) + n4 + 4n3 + 6n2 + 4n + n0

    我们把F(n)放在矩阵顶部,表示第n项的值,然后剩下的元素F(n-1)、 n4、n3、n2、n、 n0 ,去掉系数后与F(n)一起构成一个矩阵。同理,F(n+1)的矩阵也是F(n+1)在顶部,表示第n+1项的值,然后剩下的元素F(n)、 (n+1)4、(n+1)3、(n+1)2、(n+1)、 (n+1)0 去掉系数后与F(n+1)构成一个矩阵。

    好,我们现在得到了F(n)F(n+1)的矩阵,那怎么求转移过去的矩阵A呢?首先,A是一个7×7的矩阵,且根据矩阵相乘规则,可以得到矩阵A,如下图(如果对矩阵相乘不熟悉的可以先看一下矩阵相乘哈)


    图(2)

    好了,到现在为止,得到了图(2)的转移矩阵后,我们解决了前三步,还剩最后一步。怎么把这个转移矩阵应用到代码里面呢?

    题中给出了F(1) = a,F(2) = b
    所以F(3) = 2a + b + 34 = 2a + b + 24 + 4×23 + 6×22 + 4×21 + 20


    图(3)

    为了表述方便。图(3)乘号左边的我们称为转移矩阵A,乘号右边的四个矩阵分别为B2B3B4B5……

    且我们可以得到的信息有
    B2 × A = B3、B3 × A = B4、B4 × A = B5 ……
    B2 × A2 = B4、B2 × A3 = B5、B2 × A4 = B6 ……
    然后我们惊奇的发现:B2 × An-2 = Bn(fn)

    B2的数据都是已知的,所以现在我们需要得到的只是An-2的数据,在 上一篇文章中,我们需要得到的是An的矩阵,代码问题就解决了吧。

    再炒一下上一篇文章的冷饭,本来是根据B2->B3->B4->B5->……->Bn逐步递推得到Bn。现在我们转化为矩阵后,需要求的是A^(n-2)的数据,比如要求A^16,那我就可以直接通过快速幂A^1->A^2->A^4->A^8->A^16来得到了,复杂度由O(n)转化成了O(logn)

    完整代码

    #include <iostream>
    using namespace std;
    const long long int N = 2147493647;   //注意要用 long long int
    void Matrix(long long int (&a)[7][7],long long int b[7][7]){ //还是一样的函数
        long long int tmp[7][7] = {0};
        for(int i = 0; i < 7; ++i)
            for(int j = 0; j < 7; ++j)
                for(int k = 0; k < 7; ++k)
                    tmp[i][j] = (tmp[i][j] + a[i][k] * b[k][j]) % N;
        for(int i = 0; i < 7; ++i)
            for(int j = 0; j < 7; ++j)
                a[i][j] = tmp[i][j];
    }
    int main(int argc, const char * argv[]) {
        long long int sum,T,a,b,n;
        cin>>T;
        while(T--){
            cin>>n>>a>>b;
            if(n==1){
                cout<<a<<endl;
                continue;
            }
            //为了方便读者理解,直接这么定义矩阵了
            long long int temp[7][7] = {1, 2, 1, 4, 6, 4, 1,
                                        1, 0, 0, 0, 0, 0, 0,
                                        0, 0, 1, 4, 6, 4, 1,
                                        0, 0, 0, 1, 3, 3, 1,
                                        0, 0, 0, 0, 1, 2, 1,
                                        0, 0, 0, 0, 0, 1, 1,
                                        0, 0, 0, 0, 0, 0, 1};
            //temp表示的是A^n,cot表示的是最后的结果
            long long int cot[7][7] = {1, 0, 0, 0, 0, 0, 0,
                                       0, 1, 0, 0, 0, 0, 0,
                                       0, 0, 1, 0, 0, 0, 0,
                                       0, 0, 0, 1, 0, 0, 0,
                                       0, 0, 0, 0, 1, 0, 0,
                                       0, 0, 0, 0, 0, 1, 0,
                                       0, 0, 0, 0, 0, 0, 1};
            n -= 2;
            while(n){   //还是这5行核心代码
                if(n & 1) Matrix(cot,temp);
                Matrix(temp,temp);
                n /= 2;
            }
            sum = 0;  //在得到A^(n-2)之后,还需要将B2*A^(n-2)得到Bn,而sum表示的是Bn的最顶部的元素值
            sum = (sum + b*cot[0][0])%N;
            sum = (sum + a*cot[0][1])%N;
            sum = (sum + 16*cot[0][2])%N;
            sum = (sum + 8*cot[0][3])%N;
            sum = (sum + 4*cot[0][4])%N;
            sum = (sum + 2*cot[0][5])%N;
            sum = (sum + cot[0][6])%N;
            cout<<sum<<endl;
    
        }
        return 0;
    }

    较上篇文章,代码部分并没有多少变化,这篇文章着重讲了怎么根据递推公式求得转移方程并应用的过程。接下来可能会扩展一些矩阵快速幂的用法,比如不同的路径数量,行动方案等



  • 相关阅读:
    微服务下的持续集成-Jenkins自动化部署GitHub项目
    JDK新特性-Lambda表达式的神操作
    ActiveMQ详细入门教程系列(一)
    程序员必须了解的知识点——你搞懂mysql索引机制了吗?
    面试之后,扼腕叹息。
    哎,这让人抠脑壳的 LFU。
    延迟消息的五种实现方案
    Java实现Kafka生产者和消费者的示例
    Pytorch训练时显存分配过程探究
    Python命令行参数定义及注意事项
  • 原文地址:https://www.cnblogs.com/mhpp/p/7661811.html
Copyright © 2011-2022 走看看