zoukankan      html  css  js  c++  java
  • 矩阵快速幂-魔力手环

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

    题目是这样的:
      小易拥有一个拥有魔力的手环上面有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("
    ");
            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循环进行初始化赋值。
      使用矩阵快速幂计算矩阵的高次幂是非常高效的算法。

  • 相关阅读:
    作业信息获取
    SQL Server数据库性能监控计数器
    c# 索引器學習
    C# DataGridView 小技巧
    C# DataTable 小技巧
    c# 小技巧
    C# new,Virtual,abstract,sealed,Override的簡單理解
    C# 位操作符
    C# 類型小技巧
    C# partial 局部类型
  • 原文地址:https://www.cnblogs.com/aicanxxx/p/6916423.html
Copyright © 2011-2022 走看看