【蓝桥杯题解】矩阵翻硬币

历届试题 矩阵翻硬币
时间限制:1.0s 内存限制:256.0MB
提交此题
问题描述
  小明先把硬币摆成了一个 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^1000(10的1000次方)
题目最终转化求sqrt(n)*sqrt(m)(开方出来取整),由于题目数据太大(10的1000次方),因此实际考点是大数的开方和乘法:
大数乘法:模拟笔算即可

/*大数相乘函数
*知识点1:m位数和n位数相乘最多有n+m位数
*步骤1.移位操作
*步骤2.进位操作
*步骤3.去除高位0
*步骤4.转化为字符串
*/
string strMul(string a, string b)
{
    int len1 = a.length();
    int len2 = b.length();
    int i, j;
    vector<int>num(len1 + len2, 0);//最多len1+len2位
    //步骤一
    //遍历相乘,i+j相等的要相加,暂存在num中,num中由低位到高位存储,方便进位
    for (i = 0; ifor (j = 0; j1 + len2 - 1 - i - j] += (a[i] - '0')*(b[j] - '0');
        }
    //步骤二
    //进位操作,由低位向高位进位
    for (i = 0; i1; i++)
    {
        num[i + 1] = num[i + 1] + num[i] / 10;
        num[i] = num[i] % 10;
    }
    //步骤三
    //去除高位的多余的零
    i = len1 + len2 - 1;
    while (i>=0)
    {
        if (num[i] != 0)
            break;
        i--;
    }
    //步骤四
    //将结果转化成字符串
    string result = "";
    while (i>=0)
    {
        result += num[i--] + '0';
    }
    return result;
}

大数开方:

/*
*知识点1:对n位数开方,开方出来的数的位数:n是偶数为n/2位,n是奇数为n/2+1位;
*知识点2:大数比较(见后面)
*步骤1:确定开方出来的数的位数
*步骤2:从高位向低位确定每一位的值
*/
string StrQurt(string a)
{
    int len = a.length();
    //对n位数开方,开方出来的数的位数:n是偶数为n/2,n是奇数为n/2+1;
    len = len % 2 == 0 ? len / 2 : len / 2 + 1;
    string ans = "";
    for (int i = 0; i < len;i++)
    {
        ans +='0';
        int j;
        //确定当前位从0-9遍历判断
        for (j = 0; j <= 9;j++)
        {
            ans[i] = j + '0';
            int key = StrCompare(StrMul(ans + string(len - 1 - i, '0'), ans + string(len - 1 - i, '0')), a);
            if (key == 0)return ans + string(len - 1 - i, '0');
            else if (key==1)break;
        }
        if (j<10)
            ans[i]--;
    }
    return ans;
}
/*大数比较函数
*a>b return 1
*areturn -1
*a=b return 0
*/
int StrCompare(string a,string b)
{
    if (a.length() < b.length())
        return  -1;
    else if (a.length() > b.length())
        return  1;
    else        
        return a.compare(b);
}

题目到此基本已经没有多大问题了,但是在确定开方数的每一位值时会花费大量的时间,因为后面有多个零做乘法,这是非常耗时的,提供两个方法优化:
一、优化乘法算法(网上有很多,自己找)
二、将零相乘的部分剔除开来,重新写一个新的比较函数,传入比较函数有三个参数StrCmp(已经确定的高位的值得乘方,2*未确定的低位0的个数,另一个要比较的数 )

//大数比较函数
int strCmp(string a, int pos, string b)
{
    int i;
    if (a.length() + pos>b.length())
        return 1;
    if (a.length() + poslength())
        return -1;
    if (a.length() + pos == b.length())
    {
        for (i = 0; ilength(); i++)
        {
            if (i < a.length())
            {
                if (a[i]return -1;
                if (a[i] == b[i])
                    continue;
                if (a[i]>b[i])
                    return 1;
            }
            else
            {
                if (b[i]>'0')return -1;
            }

        }
    }
    return 0;
}

相应的开方函数为:

//大数开方函数
/*
*知识点1:对n位数开方,开方出来的数的位数:n是偶数为n/2位,n是奇数为n/2+1位;
*知识点2:大数比较
*步骤1:确定开方出来的数的位数
*步骤2:从高位向低位确定每一位的值
*/
string StrQurt(string a)
{
    int len = a.length();
    //对n位数开方,开方出来的数的位数:n是偶数为n/2,n是奇数为n/2+1;
    len = len % 2 == 0 ? len / 2 : len / 2 + 1;
    string ans = "";
    for (int i = 0; i < len;i++)
    {
        ans +='0';
        int j;
        //确定当前位从0-9遍历判断
        for (j = 0; j <= 9;j++)
        {
            ans[i] = j + '0';
            int key = strCmp(StrMul(ans, ans),2*(len-1-i),a);
            if (key == 0)return ans + string(len - 1 - i, '0');
            else if (key==1)break;
        }
        if (j<10)
            ans[i]--;
    }
    return ans;
}

完整代码如下:

#include
#include
#include
#include 
using namespace std;
//
///*
/*大数相乘函数
*知识点1:m位数和n位数相乘最多有n+m位数
*步骤1.移位操作
*步骤2.进位操作
*步骤3.去除高位0
*步骤4.转化为字符串
*/
string StrMul(string a, string b)
{
    int len1 = a.length();
    int len2 = b.length();
    int i, j;
    vector<int>num(len1 + len2, 0);//最多len1+len2位
    //步骤一
    //遍历相乘,i+j相等的要相加,暂存在num中,num中由低位到高位存储,方便进位
    for (i = 0; ifor (j = 0; j1 + len2 - 1 - i - j] += (a[i] - '0')*(b[j] - '0');
        }
    //步骤二
    //进位操作,由低位向高位进位
    for (i = 0; i1; i++)
    {
        num[i + 1] = num[i + 1] + num[i] / 10;
        num[i] = num[i] % 10;
    }
    //步骤三
    //去除高位的多余的零
    i = len1 + len2 - 1;
    while (i>=0)
    {
        if (num[i] != 0)
            break;
        i--;
    }
    //步骤四
    //将结果转化成字符串
    string result = "";
    while (i>=0)
    {
        result +=  num[i--] + '0';
    }
    return result;
}
//大数比较函数
int strCmp(string a, int pos, string b)
{
    int i;
    if (a.length() + pos>b.length())
        return 1;
    if (a.length() + posreturn -1;
    if (a.length() + pos == b.length())
    {
        for (i = 0; iif (i < a.length())
            {
                if (a[i]return -1;
                if (a[i] == b[i])
                    continue;
                if (a[i]>b[i])
                    return 1;
            }
            else
            {
                if (b[i]>'0')return -1;
            }

        }
    }
    return 0;
}
//大数开方函数
/*
*知识点1:对n位数开方,开方出来的数的位数:n是偶数为n/2位,n是奇数为n/2+1位;
*知识点2:大数比较
*步骤1:确定开方出来的数的位数
*步骤2:从高位向低位确定每一位的值
*/
string StrQurt(string a)
{
    int len = a.length();
    //对n位数开方,开方出来的数的位数:n是偶数为n/2,n是奇数为n/2+1;
    len = len % 2 == 0 ? len / 2 : len / 2 + 1;
    string ans = "";
    for (int i = 0; i < len;i++)
    {
        ans +='0';
        int j;
        //确定当前位从0-9遍历判断
        for (j = 0; j <= 9;j++)
        {
            ans[i] = j + '0';
            int key = strCmp(StrMul(ans, ans),2*(len-1-i),a);
            if (key == 0)return ans + string(len - 1 - i, '0');
            else if (key==1)break;
        }
        if (j<10)
            ans[i]--;
    }
    return ans;
}
int main()
{
    string a, b;
    while (cin >> a>>b)
    {
        cout << StrMul(StrQurt(a),StrQurt(b)) << endl;
    }
    return 0;
}

你可能感兴趣的:(解题报告,蓝桥杯)