矩阵快速幂-魔力手环

题目来源自牛客网的网易算法题,如有侵权行为请告知删除。

题目是这样的:
  小易拥有一个拥有魔力的手环上面有n个数字(构成一个环),当这个魔力手环每次使用魔力的时候就会发生一种奇特的变化:每个数字会变成自己跟后面一个数字的和(最后一个数字的后面一个数字是第一个),一旦某个位置的数字大于等于100就马上对100取模(比如某个位置变为103,就会自动变为3).现在给出这个魔力手环的构成,请你计算出使用k次魔力之后魔力手环的状态。

输入描述:
  输入数据包括两行:
  第一行为两个整数n(2 ≤ n ≤ 50)和k(1 ≤ k ≤ 2000000000),以空格分隔
  第二行为魔力手环初始的n个数,以空格分隔。范围都在0至99.
输出描述:
  输出魔力手环使用k次之后的状态,以空格分隔,行末无空格。
输入例子:
  3 2
  1 2 3
输出例子:
  8 9 7

  看到题目的第一反应使用2个for循环嵌套,这样可以实现,而且代码简单:

    function main(){
       var num=readLine().split(' ');
       var arr=readLine().split(' ');
       var n=num[0];
       var k=num[1];
       var temp=0;
       arr=arr.map(function(item){
           return +item;//将字符类型转化为数字
       })
       for(var i=0;i<k;i++){
           temp=arr[0];
           for(var j=0;j<n;j++){
               if(j==n-1){
                   arr[j] += temp;
               }else{
                   arr[j]+=arr[j+1];
               }
               if(arr[j]>=100){
                   arr[j] %=100;
               }
           }
       }
       console.log(arr.join(' ');
   }

  但是由于k的次数过大,这样导致时间复杂度过大,超过了规定时间1s,在牛客网上的通过率只有30%,故只能另辟蹊径。
  可以将输入看成是一维数组A,这种奇特的变化可以看成是A与n维数组B的乘积。比如输入数组A=[1,2,3],则这个B则为3 * 3的矩阵,即[ [1,0,1] ,[1,1,0] ,[0,1,1] ],故一次变化的结果为A * B。变化k次则结果为A * B^k,这就涉及到矩阵快速幂算法。

矩阵快速幂

  矩阵快速幂是用来计算矩阵n次幂的一个复杂度较小的算法,它能将矩阵n次幂的复杂度 O(n) 降低为 O(log n) 。一般情况下A^n是将A重复乘n次,而矩阵的快速幂是将矩阵运用结合律的方式降低运算次数。比如 A^15 可以写成:A^5 * A^5 * A^5 ,这样就将运算次数从15次降低到了6次;然而怎样用数学方式解释这种结合的规律。由于所有的数都可由20,21,2^2,……等线性表示,将k转化成二进制,比如k=15可以表示成1111,则 A^15 可以表示为A^8 * A^4 * A^2 * A 表示,其中A4可以由两个A2相乘,A8可以由两个A4相乘,明显降低了运算复杂度,这样结合的复杂度为 O(log n)。

核心算法如下:

    while(k){
        if(k&1){
            A=A*B;
        }
        k>>=1;
        B=B*B;
    }

  k为矩阵相乘的次数,A可以是单位阵,也可以是要和B^k次相乘的矩阵,比如k=9时,上述代码实现了A= ( k1 * B^8 ) * ( k2 * B^4 ) * ( k3 * B^2 ) * ( k4 * B),其中k1,k2,k3,k4……为k从左往右的每一位2进制数,即9的二进制数1001,依次对应k1,k2,k3,k4。

魔力手环

  该题若想减小时间复杂度,则需要使用矩阵的快速幂进行计算。A中数组的变化可以看成是与矩阵B的乘积,即B为转移矩阵,k次变化后的结果就是对A*B^k求余,矩阵B根据题目可以迅速构建出来,由于JavaScript中没有直接声明二维数组的方法,故只能在数组中嵌套数组,并且数组的初始值均为undefined,如要进行计算,需用for循环对数组初始化赋值,否则结果均为NaN。由于题目中输入的数组A是1xn的,程序中将一阶的矩阵乘法与N阶的矩阵乘法分开计算的。

程序如下:

    process.stdin.resume();//回复输入流
    process.stdin.setEncoding('utf8');
    
    var input_stdin = "";//输入的全部数据
    var input_stdin_array = "";//输入的每行数据以数组形式存在
    var input_currentline = 0;//输入的行数
    
    process.stdin.on('data', function (data) {//接收输入的数据
        input_stdin += data;
        if(data.slice(0,-1)==''){
            process.stdin.emit('end');//输入空的回车结束输入
        }
    });
    
    process.stdin.on('end', function () {//end触发
        input_stdin_array = input_stdin.split("\n");
        main();//对输入进行操作
    });
    
    function readLine() {
        return input_stdin_array[input_currentline++];//读取每一行的数据
    }
    
    //n阶矩阵的乘法与取余,x:nxn,y:nxn
    function mulOrder(x,y,n,c){
        var mul=[];//mul为全0矩阵,mul:nxn
        for(var i=0;i<n;i++){
            mul[i]=[];
            for(var j=0;j<n;j++){
                mul[i][j]=0;
                for(var k=0;k<n;k++){
                    mul[i][j] +=parseInt(x[i][k])*parseInt(y[k][j]);
                }
                mul[i][j]%=c;
            }
        }
        return mul;
    }
    //一阶矩阵的乘法与取余x:1xn,y:nxn
    function oneOrder(x,y,n,c){//一阶矩阵的乘法与取余x:1xn,y:nxn
        var mul=[];//mul为全0矩阵,mul:1xn
        for(var i=0;i<n;i++){
            mul[i]=0;
            for(var j=0;j<n;j++){
                mul[i] +=parseInt(x[j])*parseInt(y[j][i]);
            }
            mul[i]%=c;
        }
        return mul;
    }
    function main(){
        var num=readLine().split(' ');
        var arr=readLine().split(' ');
        var n=parseInt(num[0]);
        var k=parseInt(num[1]);
        var matrix=[];
        //创建矩阵B
        for(var i=0;i<n;i++){
            matrix[i]=[];
            for(var j=0;j<n;j++){
                matrix[i][j]=0;
            }
        }
        for(var i=0;i<n;i++){
            matrix[i][i]=1;
            if(i==n-1){
                matrix[0][i]=1;
            }else{
                matrix[i+1][i]=1;
            }
        }
        while(k){
            if(k&1){
                arr=oneOrder(arr,matrix,n,100);
            }
            k>>=1;
            matrix=mulOrder(matrix,matrix,n,100);
        }
        console.log(arr.join(' '));
    }

  对数组进行初始化可以使用数组中的fill()方法,然而这是es6版本中的方法,牛客网不支持,故只能使用for循环进行初始化赋值。
  使用矩阵快速幂计算矩阵的高次幂是非常高效的算法。

posted @ 2017-05-29 14:39  艾新觉罗  阅读(445)  评论(0编辑  收藏  举报