蓝桥杯 历届试题 矩阵翻硬币(大数开方)

来源:互联网 发布:淘宝开店证件照要求 编辑:程序博客网 时间:2024/05/21 19:39

历届试题 矩阵翻硬币

问题描述
  小明先把硬币摆成了一个 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;
  对于10%的数据,n、m <= 10^1000(10的1000次方)。

Q操作的实现:

import java.util.Arrays;public class Main{    // 假设0表示反面    // 小明先把硬币摆成了一个 n 行 m 列的矩阵。    static int[][] qArray = {{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0}};     /**     * 随后,小明对“每一个”硬币分别进行 "一次" Q 操作。     * 对第x行第y列的硬币进行 Q 操作的定义:将所有第 i*x 行,第 j*y 列的硬币进行翻转。     */    private static void qTurn (){        // 其中i和j为任意使操作可行的正整数 > 0,行号和列号都是从1开始。        for( int x = 1; x < qArray.length; x++ ){            for( int y = 1; y < qArray[x].length; y++ ){                for( int i = 1; i < qArray.length/x; i++ ){                    for( int j = 1; j < qArray[x].length/y; j++ ){                        // 对“每一个”硬币分别进行 "一次" Q 操作                        qArray[i * x][j * y] = qArray[i * x][j * y] == 0 ? 1 : 0;                    }                }            }        }    }    public static void main( String[] args ){        qTurn();        for(int i = 0; i < qArray.length; ++i){            System.out.println (Arrays.toString (qArray[i]));        }        System.out.println("当小明对所有硬币都进行了一次 Q 操作后,他发现了一个奇迹——所有硬币均为正面朝上。");        System.out.println("小明想知道最开始有多少枚硬币是反面朝上的。" + "于是,他向他的好朋友小M寻求帮助。\n" + "聪明的小M告诉小明,只需要对所有硬币再进行一次Q操作,"                + "即可恢复到最开始的状态。然而小明很懒,不愿意照做。" + "于是小明希望你给出他更好的方法。帮他计算出答案");        System.out.println("\n由于0表示反面,1表示正面. \n所以: \n");        int count = 0;        for(int i = 0; i < qArray.length; i++ ){            for(int j = 0; j < qArray[i].length; j++){                if(qArray[i][j] == 1){                    count++;                }            }        }        System.out.println("最开始有 " + count + "枚硬币是反面朝上的");    }}

解题思路:

小明提供了一种算法,这里演示一下 n = 2,m = 3 矩阵的翻硬币过程(1 表示 正面, 0 表示反面)


1

1

1

1

1

1

                                                   -->(x , y) =(1 , 1)      

                                                   x的倍数行,y的倍数列要翻转

0

0

0

0

0

0

                                                   -->(x , y) =(1 , 2)
                                                   x
的倍数行,y的倍数列要翻转

0

1

0

0

1

0

                                                   -->(x ,y) = (1 , 3)

0

1

1

0

1

1

                                                    -->(x , y) = (2 , 1)

0

1

1

1

0

0

                                                   -->(x ,y) = (2 , 2)

0

1

1

1

1

0

                                                  -->(x ,y) = (2 , 3)

0

1

1

1

1

1



想必大家也看出了规律,我们令初始状态都为1,然后对每一个坐标进行操作,最后值为0个数的就是开始时反面的个数。本想用暴力,但是对于 n 和 m ,如此大的数肯定不行,所以我们继续分析还有什么其他的规律。

先看 n = 1 的情况:对于(1 , m),只要看它翻转的次数的奇偶就能确定它最终的状态(次数为偶数最后肯定为正面,次数为奇数最后肯定为反面)。因为当x = 1, 确定每次第一行都要参与翻转,当 m 是 y 的倍数的时候,(1 , m)就会翻转,所以(1 , m)全过程翻转的次数取决于 m 的约数个数,1 的约数个数为1 , 3 的约数个数为2, 5 的约数个数为2, 9 的约数个数为3。也就是说当 m = k^2 (k = 1 ,2 ,3···) 其约数个数为奇数,否则 其约数个数为偶数。 因为一般数约数都是成对出现,所以必为偶数,而一个数的平方数,有两个约数相等,所以要减一个相同的就变成了奇数。

所以,对于(1 , m)来说,若m = k^2(k = 1 ,2 ,3···)则最终状态为0,否则为1。

而最后0的个数总和就是我们要的结果


再来看一般情况:(n , m)最后状态是什么?现在行的变化也是它翻转的因素。从上面容易推出,当m确定后,他的翻转次数为 n 的约数个数。
而(n , m)翻转的次数 = (n的约数个数 * m的约数个数)。刚才分析了,只有在(n, m)翻转的次数为奇数时 它的最终状态为 0。而只有 奇数*奇数 = 奇数,所以n ,m的各自的约数个数都必须为奇数,即: n = i^2 (i = 1 ,2 ,3···) 且  m = j^2 (j = 1 ,2 ,3···)。


最后得出结论:对于n行m列矩阵,经过 Q 操作后 反面的次数 count = sqrt(n) * sqrt(m) ,(即取整后再相乘)。

AC代码:

#include <iostream>#include <string>#include <cstring>using namespace std;string multiply(string str1,string str2){//字符串相乘函数    string str = "";  //最终结果    int len1 = str1.size(),len2 = str2.size();//计算两个字符串的函数    int result[1000] = {0};  //开辟数组存乘积并初始化    for(int i = len1-1; i >= 0; --i){        for(int j = len2-1; j >= 0; --j){            result[i+j+1] += (str1[i]-'0')*(str2[j]-'0');        }    }    for(int i = len1+len2-1; i >= 1; --i){//让数组的每一位都是正确的数        result[i-1] = result[i-1]+result[i]/10;        result[i] = result[i]%10;    }    int i = 0;    while(result[i] == 0)        ++i;//前导零不要    for(int j = i; j < len1+len2; ++j)//转成字符串形式        str += '0'+result[j];    return str;}int compare(string str1,string str,int pos){//字符串比较函数    int len1 = str1.size();    int len2 = str.size();    if(len2 > len1+pos)        return 0;    if(len2 < len1+pos)        return 1;    for(int i = 0; i < len2; ++i){        if(str1[i]-'0' > str[i]-'0')            return 1;        if(str1[i]-'0' < str[i]-'0')            return 0;    }}string strSqrt(string str){//对字符串开方取整函数    int len = str.size();    string str1 = "",str2 = "";    for(int i = 0; i < (len+1)/2; ++i){//若len为偶数,len/2=(len+1)/2;若len为奇数,len/2+1=(len+1)/2        for(int j = 0; j <= 9; ++j){//因为每一位的数值都是0~9            str1 = str2;            str1 += '0'+j;            if(compare(multiply(str1,str1),str,2*((len+1)/2-1-i)) == 1){                //由于str1后少了(len+1)/2-i-1个0,所以平方以后少了2*((len+1)/2-i-1)个                str2+=j-1+'0';                break;            }            if(j == 9)               str2 += '9';           }    }    return str2;}int main(){    string n,m;    cin>>n>>m;    cout<<multiply(strSqrt(n),strSqrt(m))<<endl;    return 0;}


1 0
原创粉丝点击