题目:矩阵翻硬币 链接
问题描述
小明先把硬币摆成了一个 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次方)。
题意:把硬币摆成n*m的矩阵形式,初始状态硬币全部正面朝上,然后对矩阵中每一个元素位置[x][y] (x,y从1开始),循环找i,j,若[i*x][j*y] 存在矩阵中,翻转这个位置对应的硬币。问这样的操作后有多少个硬币反面朝上。
思路:模拟之后打表找规律,设结果为cnt[n,m],z,t为某个正整数,发现当n==1时,有t^2>m>=(t-1)^2,则cnt[1,m] = t -1,即cnt = m开方后向下取整。再将n打表到10左右,发现当m == 1,n变化时,有z^2>n>=(z-1)^2,则cnt[n,1] = (z-1)。即n一定,m变化,首项为(z-1),并且容易看出n一定,cnt有变化时,每一项都相差(z-1),因而得出当n一定时,cnt[n,m]中变化的项是以(z-1)为首项,(z-1)为公差的等差数列,(m变化后cnt有变化时)每次增长的底数即是(t-1),((t-1)变化,cnt才随之改变)就是说以(t-1)为项数。即cnt[n,m] = a[t-1] = (z-1) + (t-1 -1)*(z-1)。化简得 cnt[n,m] = (t-1)*(z-1);程序化公式即为:
cnt[n,m] = floor(sqrt(n))*floor(sqrt(m));
方法:得到公式,题目就简单很多。但是数据量达到1000位整数,只能用string接收,然后手动实现公式中所需的大整数开方向下取整运算和乘法运算。
1.大整数乘法运算:直接模拟即可。将两个乘数string 翻转,方便进位。两重for循环模拟乘法每一位相乘,每一位相乘的结果所在的位置即为当前相乘两个数的位置数相加之和,即ans[i+j] += big1[i]*big2[j];之后再遍历ans中每一位,超过10就进位。
2.大整数开方并向下取整运算:假设大整数有len位,若len是奇数,则开方后结果的位数为len/2+1,若是偶数则= len/2;{证明:反向思考,开方反过来说就是两个相等的数相乘,由乘法运算可得,由于位数相同,最高位为两数位数-1相加再加1,所以 (len-1)/2+1 可得,若为偶数位说明最高位进位了,那减去那一位即可, 即(len-2)/2+1即 len/2}。开方后结果的位数有了,for循环枚举每一位的数字,再进行平方后比较,即可得每一位的数字。这样得到的string就直接是开方并取整后的结果了。
代码实现:
#include<bits/stdc++.h>
using namespace std;
const int MAXW = 2000;
string multiply(string s1,string s2){//大整数乘法
//翻转str -> int
int big1[MAXW],big2[MAXW];
int len1 = s1.length(), len2 = s2.length();
for(int i=len1-1;i>=0;i--)
big1[len1-i-1] = s1[i] - '0';
for(int i=len2-1;i>=0;i--)
big2[len2-i-1] = s2[i] - '0';
//乘法
int ans[MAXW];
memset(ans,0,sizeof(ans));
for(int i=0;i<len1;i++)
for(int j=0;j<len2;j++)
ans[i+j] += big1[i]*big2[j];
//进位
for(int i=0;i<len1+len2;i++)
if(ans[i]>=10) {
ans[i+1] += ans[i]/10;
ans[i] %= 10;
}
//找位数
int anslength = 1;
for(int i=len1+len2;i>=0;i--)
if(ans[i]!=0){
anslength = i+1;
break;
}
//get strans
string strans;
for(int i=anslength-1;i>=0;i--)
strans += ans[i] + '0';
return strans;
}
bool compare(string str1,string str2,int num){//str1 >= str2 的整数返回true
if(str1.length()<str2.length()+num) //num即为0的个数
return false;
if(str1.length()>str2.length()+num)
return true;
else if(str1.length() == str2.length()+num){
for(int i=0;i<str1.length();i++){
if(str1[i] < str2[i])
return false;
if(str1[i] > str2[i])
return true;
}
}
return true;
}
string sqrtfloor(string s){//大整数开方向下取整
int len = s.length();
if(len&1)//odd
len = len/2 + 1;
else len /= 2;
string ans = "";
for(int i=len;i>=1;i--){//结果有len位数
for(int j=9;j>=0;j--){//每一位有10中可能
string tmp = ans;
tmp += j+'0';
string res = multiply(tmp,tmp);//乘法复杂度很高,凑位数的0没有必要加进去
if(compare(s,res,(i-1)*2)){
ans += j+'0';//找到一位数,加入ans
break;
}
}
}
return ans;
}
int main(int argc, char const *argv[])
{
string str1,str2;
cin>>str1>>str2;
cout<<multiply(sqrtfloor(str1),sqrtfloor(str2))<<endl;
return 0;
}
小结:这道题做了3个小时,才得了20分。之前没有想到sqrt,用的是平方数的枚举,这样string根本实现不了,后来看的题解,才想到开方不是一样的吗??????!我当时都方了?!菜,真的菜。希望考试时不会这么坑。不知道能不能进决赛。
————蓝桥杯考试前一周。