历届试题 矩阵翻硬币
----------------------------------------------------痞子小小崔
时间限制:1.0s 内存限制:256.0MB
问题描述
小明先把硬币摆成了一个 n 行 m 列的矩阵。
随后,小明对每一个硬币分别进行一次 Q 操作。
对第x行第y列的硬币进行 Q 操作的定义:将所有第 ix 行,第 jy 列的硬币进行翻转。
其中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次方)。
首先看到 10的一千次方 我不淡定了,这肯定得涉及大数问题。
如何实现乘法和开方,大数的存储,我们使用string,这样才能存进去
大数处理:
乘法:其实有很多速度快而且更巧妙的大数乘法,但是在蓝桥杯,我们只要使用模拟手算法应该是问过,虽说是模拟,但也有一些我们常用但不太了解的规律,我们在手算乘法的时候,需要进行移位和进位,这两个操作我们会通过两步完成
1.移位:对于两个数a=12,b=25,在相乘的时候我们让每一位数分别相乘,即a[0]*b[0]=2 , a[0]*b[1]=5 , a[1]*b[0]=4 , a[1]*b[1]=10,那么规律就是,对于两个数a[i] ,
b[j],所有i+j相同的数都要加到一起,所以我们要把5+4=9合并为一个数,也就是说,将每一位数相乘之后,我们实际得到了2,9,10三个没有进位的数
2.进位:通过上面的操作,我们的2,9,10三个没有进位的数,下面就要进行进位,因为我们是把高位相乘得到的数放在前面,地位相乘得到的数放在后面,所以这三个数排列自然也是高位在前,地位在后,所以要从右向左进位,进位的方法就是**a[i]=a[i+1]/10
,
a[i+1]=a[i+1]%10,**如果放到这三个数上面,就是,9+10/10=10,然后10%10=0,这三个数变成2,10,0,再一次就是2+10/10=3,10%10=0,三个数变为3,0,0,而这正是我们要求的结果,在实际操作中,我们习惯于将数倒着存放,即将数存为10,9,2,这是为了进位方便,因为如果正序的话如果最高位发生进位我们就要将每一个数向后移动一位从而挪出一个空位,想想都麻烦,倒序的话因为是向后进位,想怎么进就怎么进开方:假如一个数有偶数位n,那么这个数的方根有n/2位;如果n为奇数,那方根为(n+1)/2位。
枚举出它的整数根
0000=0<1200
1010=100<1200
2020=400<1200
3030=900<1200
4040=1600>1200
所以,这个根的十位就是3,然后,再枚举个位
3131=961<1200
3333=1089<1200
3434=1156<1200
35*35=1225>1200
我先用暴力算,先找到规律,解决小数上的问题,然后再去解决大数问题
我首先做的就是 暴力找规律
以下为暴力找规律的代码:
//这种暴力解简直了,500X200已经达到21秒了。。。
#include<iostream>
#include<string.h>
using namespace std;
int M[500][200];
main()
{
int n,m,dep=0;
cin>>n>>m;
cout<<endl;
cout<<"布局方正:"<<endl;
memset(M,0,sizeof(M)); //memset 函数方便全部置0 所以我用0代表1 反过来
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
for(int k=1;k<=n;k++)
for(int l=1;l<=m;l++)
{
if(i*k<=n&&j*l<=m) M[i*k][j*l]=!M[i*k][j*l];
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cout<<" "<<M[i][j];
if(M[i][j]==1) dep++;
}
cout<<endl;
}
cout<<"个数:"<<dep;
return 0;
}
行数始终为1时
(1,4) 1001
(1,9) 100100001
(1,16)1001000010000001
(1,25)1001000010000001000000001
列数始终为1时
(4,1) 1001
(9,1) 100100001
(16,1)1001000010000001
(25,1)1001000010000001000000001
发现了吗!! 对是数字的平方位上有翻转的硬币
那么25X25上,行上有5个,列上有5个,总计就是5*5=25个。
现在规律解决了:输入2个数 分别求2个数的根,然后求介于哪两个数平方之间,得出行与列上有几个是翻转的,然后两者相乘,就是结果!
然后代码功能:1、求根。2、求大数之间相乘。3、找出求出的根介于哪两数之间。
我也在网上找到了不错的代码,稍微修改了一丢丢(把原来判断用的“:”条件改了),但没有注释(也许是我没找到吧),然后我好不容易看懂并加上注释,好心累,不写注释真的大丈夫?????大家也可以看看(还看不懂的,到最下面,我打了一份带输出的代码,结合注释看,再看不懂,GG)
#include<iostream>
#include<string>
#include<cstring>
using namespace std;
string strMul(string a,string b)//strmul(两数相乘) 求a与b的乘
{
//a为45 b为45
//a[0]=4 a[1]=5
//b[0]=4 b[1]=5
//
//a[0]*b[0]=16
//a[0]*b[1]=20
//a[1]*b[0]=20 i+j=1 相同 20+20=40
//a[1]*b[1]=25
//
//25 25%10=5(二) | |5 | |5 |5 |
//40 40+25/10=42(一)| |42 42%10=2(二) | |2 |2 |
//16 |>|16 16+42/10=20(一)|>|20 20%10=0(二) |0 |..
//0 | |0 | |0 0+20/10=2(一)|2 |..
//0 | |0 | |0 |0 |..
//0 | |0 | |0 |0 |..
//.....
string result="";
int len1=a.length();
int len2=b.length();
int i,j;
int num[500]={0}; //用来存每个 位 上的数
//a[0]*b[0]=16
//a[0]*b[1]=20
//a[1]*b[0]=20 i+j=1 相同 20+20=40
//a[1]*b[1]=25
for(i=0;i<len1;i++)//i与j相同的相加 并将25 40 16存入num
for(j=0;j<len2;j++)
{
num[len1-1+len2-1-i-j]+=(a[i]-'0')*(b[j]-'0');
}
//25 25%10=5(二) | |5 | |5 |5 |
//40 40+25/10=42(一)| |42 42%10=2(二) | |2 |2 |
//16 |>|16 16+42/10=20(一)|>|20 20%10=0(二) |0 |..
//0 | |0 | |0 0+20/10=2(一)|2 |..
//0 | |0 | |0 |0 |..
//0 | |0 | |0 |0 |..
//.....
i=0;i<len1+len2;i++)//进位 得出两数相乘结果 不过也许会有多余0的存在
{
num[i+1]=num[i+1]+num[i]/10;
num[i]=num[i]%10;
}
//下面2个for是一起作用的 第一个for排除其他的'0',得到num[3]=2中的i为3 然后传给第二个for
for(i=len1+len2-1;i>=0;i--)
{
if(num[i]!=0)
break;
}
for(;i>=0;i--)//第二个for 将num里的2025数字转变为字符串 送到result中
{
result+=(char)(num[i]+'0');//num为int型 所以这里用char 需要加入'0'
}
return result; //为a与b的乘
}
int strCmp(string a,string b,int pos)//strCmp(目前求出的根^2 , n或m , ''n或m'' 的长度与 ''目前求出的根^2'' 的长度的相差位数)
{
int i;
//假设 a为9 b为858 pos为2
//1+2==3
//9>8
//返回1
//根据位数 判断大小 (比如:23<223 一定)
if(a.length()+pos>b.length()) return 1; //返回1 跳出while
if(a.length()+pos<b.length()) return 0; //返回0 继续while
//当长度相同 开始比较每一位上的数来判断大小
if(a.length()+pos==b.length())
{
for(i=0;i<a.length();i++)//从第一位开始
{
if(a[i]<b[i]) return 0;
if(a[i]==b[i]) continue;
if(a[i]>b[i]) return 1;
}
}
}
string strSqrt(string a)
{
string result=""; //设立result字符串为空 此时输出什么也没有 0也没有
int i;
int len=a.length();
if(len%2==0) len=len/2;
else len=len/2+1;//a长度的根长度(理论值)
for(i=0;i<len;i++)//求根的关键 i代表位数 从第一位开始(i从0开始) 然后第二位。。!!!最后一位不在这里循环 在里面的
{
result+='0';
//strCmp(目前求出的根^2 , n或m , ''n或m'' 的长度与 ''目前求出的根^2'' 的长度的相差位数)
while(strCmp(strMul(result,result),a,2*(len-1-i))!=1)
{
result[i]++;
if(result[i]==':') break; //'9'加1为':' 一旦到10 便退出
}
result[i]--;//消除while条件判断的影响
}
return result;//strsqrt(求根)
}
int main()
{
string n,m;
cin>>n>>m;
//strmul(两数相乘)、strsqrt(求根)
cout<<strMul(strSqrt(n),strSqrt(m))<<endl;
return 0;
}
下面下面呢!! 就是带输出的代码了,结合注释, 看懂的人可以忽略不计了!!!!
#include<iostream>
#include<string>
#include<cstring>
using namespace std;
string strMul(string a,string b)
{
string result=""; cout<<" "<<"进入strMul"<<" 运行"<<"strMul("<<a<<","<<b<<")"<<" result="<<result<<endl;
int len1=a.length();
int len2=b.length(); cout<<" "<<"len1:"<<len1<<" len2:"<<len2<<endl<<endl;
int i,j;
int num[500]={0};
cout<<" "<<"进入for循环"<<endl;
for(i=0;i<len1;i++)
for(j=0;j<len2;j++)
{
cout<<" "<<"第一个循环: i="<<i<<" j="<<j<<endl;
cout<<" "<<"a["<<i<<"]="<<a[i]<<" "<<"b["<<j<<"]="<<b[j]<<endl;
num[len1-1+len2-1-i-j]+=(a[i]-'0')*(b[j]-'0');
cout<<" "<<"num["<< len1-1+len2-1-i-j<<"]="<<num[len1-1+len2-1-i-j]<<endl;
cout<<" "<<"a["<<i<<"]="<<a[i]<<" "<<"b["<<j<<"]="<<b[j]<<endl;
}
cout<<endl;
for(i=0;i<len1+len2;i++)
{
cout<<" "<<"第二个循环: i="<<i<<endl;
num[i+1]=num[i+1]+num[i]/10; cout<<" num[i+1]="<<num[i+1]<<endl;
num[i]=num[i]%10; cout<<" num[i]="<<num[i]<<endl;
}
cout<<endl;
for(i=len1+len2-1;i>=0;i--)
{
cout<<" "<<"第三个循环: i="<<i<<endl;
cout<<" num[i]="<<num[i]<<endl;
if(num[i]!=0)
break;
}
cout<<endl;
for(;i>=0;i--)
{
cout<<" "<<"第四个循环: i="<<i<<endl;
cout<<" result="<<result<<endl;
result+=(char)(num[i]+'0');
cout<<" result="<<result<<endl<<endl;
}
cout<<" "<<"result:"<<result<<endl<<endl;
return result;
}
int strCmp(string a,string b,int pos)
{
int i;
cout<<" "<<"进入strCmp: "<<" 运行strCmp("<<a<<","<<b<<","<<pos<<")"<<endl;
cout<<" "<<"a.length()+pos="<<a.length()+pos<<" b.length()="<<b.length()<<endl;
if(a.length()+pos>b.length()) {cout<<" 返回 1"<<endl;return 1;}
if(a.length()+pos<b.length()) {cout<<" 返回 0"<<endl;return 0;}
if(a.length()+pos==b.length())
{
for(i=0;i<a.length();i++)
{
cout<<" "<<" 进入循环 i="<<i<<endl;
cout<<" a[i]="<<a[i]<<" b[i]="<<b[i]<<endl;
if(a[i]<b[i]) {cout<<" 返回 0"<<endl;return 0;}
if(a[i]==b[i]) {cout<<" continue"<<endl;continue;}
if(a[i]>b[i]) {cout<<" 返回 1"<<endl;return 1;}
}
}
}
string strSqrt(string a)
{
cout<<"进入strSqrt a为: "<<a<<endl;
string result="";
int i;
int len=a.length();
if(len%2==0) len=len/2;
else len=len/2+1; cout<<" "<<"a长度的根的理论长度 len: "<<len<<endl;
for(i=0;i<len;i++)
{
cout<<" "<<"strSqrt的第"<<i<<"次循环" <<endl;
result+='0'; cout<<" "<<"result: "<<result<<endl<<endl;
cout<<" "<<"运行while(!=1)判断 "<<"a="<<a<<" 2*(len-1-i)(pos)="<<2*(len-1-i)<<endl;
cout<<" "<<"while(strCmp(strMul("<<result<<","<<result<<")"<<","<<a<<","<<2*(len-1-i)<<")"<<endl;
while(strCmp(strMul(result,result),a,2*(len-1-i))!=1)
{
cout<<" "<<"进入while"<<endl;
cout<<" "<<"pos="<< 2*(len-1-i)<<endl;
cout<<" "<<"result:"<<result<<" result["<<i<<"]="<<result[i]<<endl;
result[i]++;
if(result[i]==':') break;
cout<<" "<<"result:"<<result<<" result["<<i<<"]="<<result[i]<<endl<<endl;
cout<<" "<<"运行while(!=1)判断 "<<"a="<<a<<" 2*(len-1-i)(pos)="<<2*(len-1-i)<<endl;
cout<<" "<<"while(strCmp(strMul("<<result<<","<<result<<")"<<","<<a<<","<<2*(len-1-i)<<")"<<endl;
}
cout<<" "<<"出while"<<endl;
result[i]--;
cout<<" "<<"result:"<<result<<" result["<<i<<"]="<<result[i]<<endl<<endl;
}
return result;
}
int main()
{
string n,m;
cin>>n>>m;
cout<<strMul(strSqrt(n),strSqrt(m))<<endl;
return 0;
}
输出的样子