蓝桥杯 试题 历届试题 矩阵翻硬币 大数问题

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

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

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

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

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

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

  聪明的小M告诉小明,只需要对所有硬币再进行一次Q操作,即可恢复到最开始的状态。然而小明很懒,不愿意照做。于是小明希望你给出他更好的方法。帮他计算出答案。
输入格式
  输入数据包含一行,两个正整数 n m,含义见题目描述。
输出格式
  输出一个正整数,表示最开始有多少枚硬币是反面朝上的。
样例输入
2 3
样例输出
1
解题思路:我们可以首先考虑小数据情况,按Q操作定义暴力求解,看是否能看出求解规律加深对题目的了解。
暴力求解代码:
 1 #include<cstdio>
 2 
 3 const int Max_N = 1000;
 4 const int Max_M = 1000;
 5 
 6 int n,m; //输入
 7 
 8 bool coin[Max_N+1][Max_M+1]; //coin[x][y] = false表示正面(0) 否则反面(全局变量初始值为false) 
 9 
10 void solve()
11 {
12     for( int x=1; x<=n; x++ )
13     {
14         for( int y=1; y<=m; y++ )
15         {
16             for( int i=x; i<=n; i+=x )
17             {
18                 for( int j=y; j<=m; j+=y )
19                 {
20                     coin[i][j] = !coin[i][j];//符合条件则反转    
21                 }    
22             }    
23         }    
24     }    
25     //输出
26     printf("  ");
27     for( int y=1; y<=m; y++ )
28         printf("%d ",y);
29     printf("\n");
30     int row = 1;
31     for( int x=1; x<=n; x++ )
32     {
33         printf("%d ",row);
34         row++;
35         for( int y=1; y<=m; y++ )
36         {
37             if( coin[x][y] )    printf("%d ",1);
38             else    printf("%d ",0);    
39         }    
40         printf("\n");
41     } 
42 }
43 
44 int main()
45 {
46     scanf("%d%d",&n,&m);
47     
48     solve();
49     
50     return 0;
51 }
View Code

多次测试可以看到只有行号列号都是完全平方数的硬币开始的状态为反面朝上。


题目分析:
1)首先由题意可知只有翻转过奇数次的硬币初始状态为反面朝上,所以问题转变为如何求硬币翻转的次数。
2)由Q操作的定义:将所有第 i*x 行,第 j*y 列的硬币进行翻转,我们可以逆向思考:对行号X翻转次数产生影响的行应为X的约数,如X=8,则行号 i = 1, 2, 4, 8 的行号对X翻转次数会产生
影响,列号Y同理。
设f( x ) = x约数的个数,那么行号X列号Y的硬币被翻转的次数为 c( X,Y ) = f( X ) * f( Y )
3)又因为只有乘积的两个数都为奇数时结果为奇数,所以问题又转变为X在什么条件下其约数为奇数。答案是完全平方数。因为对于任意整数其约数是成对的,如X=8(1*8,2*4)。而对于完全
平方数,有一对约数相等,如X=9(1*9,3*3,结果为3个约数)。
题目输入为 n*m的矩阵,所以问题转变为满足 1<=i<=n的完全平方数有几个,即求出 (int)sqrt(n) * (int)sqrt(m)。
4)由于输入的规模很大,所以又涉及到C++的大数问题。
对于大数的开方问题可以用牛顿逼近法解决。(涉及到大数乘积,大数比较)

大数开方: 牛顿逼近法。

        如果一个数的位数为偶数,那么这个数的方根就有 n/2 位,如果一个数的位数为奇数,这个数的方根就有 n/2 + 1 位。

        比如 num=1000 ,那么它的位数为 4 ,即方根就有 2 位。我们从方根的最高位进行枚举。

          先枚举出它的十位:

            10 * 10 = 100 < 1000

            20 * 20 = 400 < 1000

            30 * 30 = 900 < 1000

            40 * 40 = 1600 > 1000

          则这个根的十位为 3 。

          再枚举它的个位:

            31 * 31 = 961 < 1000

            32 * 32 = 1024 > 1000

          则这个根的个位为 1 。即这个方根为 31 。

解决代码:

#include<iostream>
#include<string>
using namespace std;

const int Max_N = 1000000; //10^1000000

/*大数相乘 实现是模拟手算
 *比如23*46 a[]={2,3} b[]={4,6}
 *先让a[i]与b[j]的每一位相乘 再让i+j相等的位数相加(注意为了进位方便
 数字的保存是按低位到高位保存 与输入时相反 所以要对称的处理)
 接着每一位的数字判断是否大于10 大于10则要进位 
 */ 
string strMultiply(string str1,string str2)
{
    int len = str1.length() + str2.length() - 2;
    int num[Max_N/2] = {0,}; //根据牛顿逼近法 10^1000000最大位数为/2 
    //先用int[]保存 i+j相等的乘积的和 
    for( int i=0; i<str1.length(); i++ )
    {
        for( int j=0; j<str2.length(); j++ )
        {
            num[len-(i+j)] += (str1[i]-'0')*(str2[j]-'0');
        }
    }
    //模拟手算
    for( int i=0; i<=len; i++ )
    {
        num[i+1] += num[i]/10;
        num[i] %= 10;    
    } 
    //在将数值用string保存 这时数的高位在string的低位 
    int k = len;
    if( num[len+1] )    k++; 
    string strResult = "";
    for( int i=k; i>=0; i-- )
    {
        strResult += num[i]+'0';
    }
    return strResult;
}
 
//比较大数大小 若str1*10^pos<=str2返回1 否则返回0 
int compare(string str1,string str2,int pos)
{
    int len1 = str1.length(), len2 = str2.length();
    
    if( len1+pos!=len2 )//首先判断位数是否相等 
    {
        return (len1+pos<len2) ? 1:0;
    }
    for( int i=0; i<len1; i++ )//若位数不等逐位比较大小 
    {
        if( str1[i]>str2[i] )    return 0;
        if( str1[i]<str2[i] )    return 1;    
    }
    return 1; //最后str1后面的都是0 所以一定<= 
} 
//对大数开方  牛顿逼近法 
string strSqrt(string str)
{
    int len = str.length()/2 + str.length()%2; //开方结果的位数
    string strResult = "";//保存最终结果 
    while( len )
    {    //牛顿逼近法 从大到小逐位测试 选第一个符合条件的  
        len--;
        for( int i=9; i>=0; i-- )
        {
            string strTemp = strResult;
            strTemp += (i+'0'); //保存中间结果 
            if( compare(strMultiply(strTemp,strTemp),str,2*len) )
            {
                strResult = strTemp;
                break;
            }
            //比较的是(strTemp*10^len)^2 
        }    
    }    
    return strResult;
}

int main()
{
    string strN,strM;
    
    cin>>strN>>strM;

    cout<<strMultiply(strSqrt(strN),strSqrt(strM))<<endl;//即输出n,m开方的乘积 
    
    return 0;
}

//主要参考了https://www.cnblogs.com/-rainbow-/p/8666587.html的思路。

猜你喜欢

转载自www.cnblogs.com/w-like-code/p/13386013.html