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

历届试题 矩阵翻硬币

问题描述
  小明先把硬币摆成了一个 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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值