幂一矩阵——详解第五届蓝桥杯决赛题目

来源:互联网 发布:renee数据恢复 编辑:程序博客网 时间:2024/06/15 17:20

题目要求:

幂一矩阵

天才少年的邻居 atm 最近学习了线性代数相关的理论,他对“矩阵”这个概念特别感兴趣。矩阵中有个概念叫做幂零矩阵。对于一个方阵 M ,如果存在一个正整数 k 满足 M^k = 0 ,那么 M 就是一个幂零矩阵。(^ 表示乘方)

atm 不满足幂零矩阵,他自己设想了一个幂一矩阵:对于一个方阵 M ,如果存在一个正整数 k 满足 M^k = I ,其中 I 是单位矩阵,那么 M 就是一个幂一矩阵。

atm 特别钟情于这样一种方阵:每行每列有且仅有一个 1 。经过 atm 不断实验,他发现这种矩阵都是幂一矩阵。

现在,他的问题是,给定一个满足以上条件的方阵,他想求最小的 k 是多少。

【输入格式】
第一行一个正整数 n ,表示矩阵大小是 n * n 。
接下来 n 行,每行两个正整数 i j 表示方阵的第 i 行第 j 列为 1。
1 <= i, j <= n 。
行号,列号都从1开始。

【输出格式】
一行。一个正整数,即题目中所说最小的 k 。

【样例输入】
5
3 1
1 2
4 4
2 3
5 5

【样例输出】
3

【数据范围】
对于 30% 的数据满足 n <= 10
对于 60% 的数据答案不超过 10^18
对于 100% 的数据满足 n <= 10000

资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 1000ms

请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。

所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
注意:不要使用package语句。不要使用jdk1.7及以上版本的特性。
注意:主类的名字必须是:Main,否则按无效代码处理。

这道题目是关于矩阵相乘的,理解起来应该问题不大,但是要做出来得考虑以下几个问题:

  1. 问:输入矩阵如何存储?用二维数组?
  2. 问:矩阵的幂要怎样计算?用常规得三层for循环计算?
  3. 问:中间结果怎样存储?也用二维数组?
  4. 问:要一步步累乘算幂吗?有没有捷径?
  5. 问:怎样确定已经得出最终结果?判断是否得出单位矩阵?

这5个主要问题都很棘手,因为任何一个处理不好都会导致算法时间复杂度或者空间复杂度过高,导致超出题目限制:峰值内存消耗(含虚拟机) < 256M,CPU消耗 < 1000ms

下面我来分享下我的解法,不敢说是最优解法,但是可以在满足题目要求的时间和空间要求下解决问题,抛砖引玉,如果哪位大牛有更完美的解法,欢迎留言交流^_^

下面进入正题,先一一解答上面的5个棘手的问题:

  1. 问:输入矩阵如何存储?用二维数组?
    答:用二维数组肯定是不可取的,因为矩阵里面每行每列只有一个1啊,其他都是0,用二维数组会引起巨大的空间浪费,题目中给出n<=10000,当n=10000时,二维数组大小为10000*10000,会很容易引起java.lang.OutOfMemoryError: Java heap space!空间复杂度过高!那用什么存储?答案是:索引数组!

    索引数组是个一维数组,因为矩阵中每行或者每列只有有个1,我们只要存储那个1的位置就可以了,这里以行为例:每行都有且只有一个1,那就可以把行作为数组的下标,把该行中1的列值作为数组的值(例如:n=5,那就用个1行5列的一维数组num[5],如果第3行第2列位置为1,那就令num[3] = 2),这就把二维变一维了,时间复杂度空间复杂度直接从O(n^2)降到了O(n),很爽有木有,而且不用排序,即使随机输入,也可以自动按行进行排序,因为数组索引是从0依次递增的。也正因为巧妙的利用数组索引进行存储,所以这个一维数组也叫索引数组

  2. 问:矩阵的幂要怎样计算?用常规得三层for循环计算?
    答:用常规三层for循环来计算显然不可取,时间复杂度太高(O(n^3))!而且问题1中既然没有选择用二维数组存储,显然也不能用常规算法计算。那么问题来了,索引数组怎样计算?要回答这个问题,必须从数学角度理解这个计算过程,单纯解释概念难以理解,下面以题目中给的实例,来解释这个过程(仔细看,这是这道题目的解题核心!):

    【样例输入】
    5
    3 1
    1 2
    4 4
    2 3
    5 5

原矩阵为5*5,用常规表示方法为:、
这里写图片描述
用题目中的表示方法为

1 2
2 3
3 1
4 4
5 5

原矩阵的平方,结果的常规表示方法为:
这里写图片描述

用题目中表示方法为:

1 3
2 1
3 2
4 4
5 5

下面开始找规律 (~ ̄▽ ̄)~ :

原矩阵 矩阵的平方 1 2 1 3 2 3 2 1 3 1 3 2 4 4 4 4 5 5 5 5

如果你已经找到,那么恭喜你,线性代数学的不错 ヽ( ̄▽ ̄)ノ,找不到的童鞋,看下面解析:
原矩阵有1 2和2 3,所以矩阵的幂就有1 3(中间消去共有的2),同理原矩阵有2 3和3 1,所以幂值就有2 1(消去3),同理可得后面答案(具体为何会有这样的规律,这里不不解释,需要的同学可以翻翻线性代数书)
以此类推,如果计算矩阵的3次幂,我想你也能直接写出结果,没错结果就是个单位阵,这里就不再赘述了,下面来看第3个问题

3.问:中间结果怎样存储?也用二维数组?
答: 其实问题2解决了,问题3也就解决了,因为不需要存储中间结果,直接类似于遍历链表一样,从原数组的索引开始,一步步索引搜索,直到数组的索引和值相等时,记录这个过程索引搜索的次数,这个次数就是幂值,最后所有次数求最小公倍数即可(因为只有所有列的值都同时满足索引和值相等才能算是单位阵)!

4.问:要一步步累乘算幂吗?有没有捷径?
答:显然这个问题也迎刃而解,不要一步步累乘,直接索引搜索然后记录搜索次数,就可以得出最终结果

5.问:怎样确定已经得出最终结果?判断是否得出单位矩阵?
答:最终结果不需要判断单位阵,只要算出索引次数最小公倍数就是最终结果

解决了这5个问题,这道题目已经可以写出来了,下面是代码,其实就是上面思想的计算机实现:

package _2014年国赛B组;import java.util.Scanner;public class 第五题 {    public static void main(String[] args) {        Scanner in = new Scanner(System.in);        int n = in.nextInt();        //创建索引数组num[n]        int[] num = new int[n];        for(int i = 0;i<n;i++){            //输入数组,注意要-1,因为题目说输入的行和列都是以1开始            num[in.nextInt()-1] = in.nextInt()-1;        }        //这里vis数组为了加快循环        boolean[] vis = new boolean[n];        //max存储最终结果        long max = 1;        //从原数组的第0行开始,寻找搜索        for(int i = 0;i<n;i++){            //如果当前行已经被搜索过,直接continue            if(vis[i]){                continue;            }            //count记录搜索次数(题目说对于 60% 的数据答案不超过 10^18 ,这里以防万一,用long)            long count = 1;            //s为每次搜索的数组下标索引            int s = i;            //当前第s行被搜索了,记录vis数组            vis[s] = true;            //当索引与值不相等时就继续循环            while(num[s] != i){                //每次循环一次count+1                count++;                //当前索引对应的值是下一次的索引                s = num[s];                //当前索引对应的行被搜索过,记录vis数组                vis[s] = true;            }            //搜索结束,判断搜索结果是否能被max整除            if(max%count!=0){                //计算max和count的最小公倍数存入max                if(max>count){                    max = max*count/gcd(max,count);                }else{                    max = max*count/gcd(count,max);                }            }        }        //输出最终结果        System.out.println(max);        in.close();    }    private static long gcd(long max, long count) {        //辗转相除法求最大公约数        if(max%count==0){            return count;        }        return gcd(count,max%count);    }}

运行结果:

53 11 24 42 35 53

测试用时:<1ms

另一组测试用例:

99991   99992   13   24   35   46   57   68   79   810  9...  ...(省略部分数据)9986    99859987    99869988    99879989    99889990    99899991    99909992    99919993    99929994    99939995    99949996    99959997    99969998    99979999    99989999

测试用时:<1ms

0 0
原创粉丝点击