历届试题 矩阵翻硬币
时间限制: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;
}