矩阵翻硬币

一、题目

小明先把硬币摆成了一个 n 行 m 列的矩阵。

    随后,小明对每一个硬币分别进行一次 Q 操作。

    对第x行第y列的硬币进行 Q 操作的定义:将所有第 i*x 行,第 j*y 列的硬币进行翻转。

    其中i和j为任意使操作可行的正整数,行号和列号都是从1开始。

    当小明对所有硬币都进行了一次 Q 操作后,他发现了一个奇迹——所有硬币均为正面朝上。

    小明想知道最开始有多少枚硬币是反面朝上的。于是,他向他的好朋友小M寻求帮助。

    聪明的小M告诉小明,只需要对所有硬币再进行一次Q操作,即可恢复到最开始的状态。然而小明很懒,不愿意照做。于是小明希望你给出他更好的方法。帮他计算出答案。

【数据格式】
    输入数据包含一行,两个正整数 n m,含义见题目描述。
    输出一个正整数,表示最开始有多少枚硬币是反面朝上的。

【样例输入】
2 3

【样例输出】
1

【数据规模】
对于10%的数据,n、m <= 10^3;
对于20%的数据,n、m <= 10^7;
对于40%的数据,n、m <= 10^15;
对于100%的数据,n、m <= 10^1000101000次方)。

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


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

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

二、思路分析

刚开始猛的一看这个题的时候感觉挺简单的,不就是对矩阵的每一个元素做一次Q操作嘛,于是”一通操作猛如虎,一看战绩零杠5”,写出了下面的代码。

public class Main {
    static int a[][];
    static int n;
    static int m;

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String s[] = scanner.nextLine().split(" ");
        n = Integer.parseInt(s[0]);
        m = Integer.parseInt(s[1]);
        a = new int[n + 1][m + 1];
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                a[i][j] = 1;
            }
        }
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                Q(i, j);
            }
        }
        /*
         * for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) {
         * System.out.print(a[i][j]); } System.out.println(); }
         */
        int count = 0;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                if (a[i][j] == 0) {
                    count++;
                }
            }
        }
        System.out.println(count);
    }

    // 对(x,y)进行Q操作
    public static void Q(int x, int y) {
        for (int i = 1; i * x <= n; i++) {
            for (int j = 1; j * y <= m; j++) {
                reverse(i * x, j * y);
            }
        }
    }

    // 对(x,y)进行翻转操作
    public static void reverse(int x, int y) {
        if (a[x][y] == 0) {
            a[x][y] = 1;
        } else {
            a[x][y] = 0;
        }
    }

}

输入了测试用例,然后一看结果正确,OK,本以为完事了,一看最下面的数据规模就知道写的代码要GG了,对于100%的数据,n、m <= 10^1000(10的1000次方),而我写的代码最多到10%的数据。
掐指一算,肯定是做法不对,,,后查阅资料后得出下面

三、正确做法

Q 操作的定义:将所有第 i*x 行,第 j*y 列的硬币进行翻转。
(一)、找规律。
1、先看1行m列的矩阵,因为只有1行,当对其进行Q操作的时候,i和x都只能为1,
假设m为5,在对1行5列的矩阵的每个元素进行了Q操作后,推导过程如下:
初始状态:1 1 1 1 1
对(1,1)元素进行Q操作,此时变成0 0 0 0 0
对(1,2)元素进行Q操作,此时变成0 1 0 1 0
对(1,3)元素进行Q操作,此时变成0 1 1 1 0
对(1,4)元素进行Q操作,此时变成0 1 1 0 0
对(1,5)元素进行Q操作,此时变成0 1 1 0 1
综上,可以看出,这五个元素分别翻转了1,2,2,3,2次,很明显,当进行奇数次翻转后为0,当进行偶数次翻转后变为1。那么,我们如果要求最终有几个0,便是求有几个位置可以进行奇数次翻转
首先,设1<=x<=m,对于(1,m)矩阵中的(1,x)硬币,它翻转了奇数次呢?还是偶数次呢?这时回看到Q操作的定义,会发现这样一个规律:(1,x)位置翻转的次数等于x的约数个数,且当x=k^2(k=1,2,3,4,5,6)时,翻转次数为奇数次,否则为偶数次。
如1行5列矩阵中:
1的约数只有1个,因此(1,1)翻转了1次,
2的约数有2个,因此(1,2)翻转了2次,
3的约数有2个,因此(1,3)翻转了2次,
4的约数有3个,因此(1,4)翻转了3次,
5的约数有2个,因此(1,5)翻转了2次。
当k=1,2时,即x=1,4时翻转奇数次,其他为偶数次。

综上,一个(1,m)矩阵在对每一个元素都进行Q操作后,0的个数为根号下m

2、那么(n,m)矩阵呢?这里不再赘述,找个简单的矩阵再走一遍流程就会得到下面的结论。
一个(n,m)矩阵在对每一个元素都进行Q操作后,0的个为根号下n乘以根号下m
参考:http://blog.csdn.net/snailset/article/details/26752435

(二)、处理大数开根。
对于100%的数据,n、m <= 10^1000(10的1000次方)。
n,m的最大值是10^1000次方,那么普通的开根已经不适用于这种情况了,在Java中有BigInteger和BigDecimal可以满足这样大数开根的需求。
参考:https://www.cnblogs.com/Annetree/p/6664383.html
最后得出以下代码:

public class Demo {

    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);
        BigInteger n = scanner.nextBigInteger();
        BigInteger m = scanner.nextBigInteger();
        BigInteger tem = sqrt(n).multiply(sqrt(m));
        System.out.println(tem);

    }
    //大数开根--->折半查找法
    static BigInteger sqrt(BigInteger x) {
        BigInteger l = BigInteger.ONE;// 1
        BigInteger r = x;// 10000000000000000000000000
        BigInteger temp = BigInteger.ZERO;// 0
        while (!l.equals(r)) {
            BigInteger mid = (l.add(r)).divide(BigInteger.valueOf(2));// (l+r)/2
            // temp!=0&&temp==mid---->mid!=0
            if (temp.compareTo(BigInteger.ZERO) != 0 && temp.compareTo(mid) == 0) {
                break;
            } else {
                temp = mid;
            }
            if (temp.compareTo(BigInteger.ZERO) == 0) {
                temp = mid;
            }
            // mid*mid>x
            if (mid.multiply(mid).compareTo(x) == 1) {
                r = mid;
            } else {
                l = mid;
            }
        }
        if (l.multiply(l).compareTo(x) == 1) {
            l = l.subtract(BigInteger.ONE);
        }
        return l;

    }
}

The greatest test of courage on earth is to bear defeat without losing heart.

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值