矩阵快速幂-魔力手环
题目来源自牛客网的网易算法题,如有侵权行为请告知删除。
题目是这样的:
小易拥有一个拥有魔力的手环上面有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循环进行初始化赋值。
使用矩阵快速幂计算矩阵的高次幂是非常高效的算法。