[蓝桥杯] 历年试题 矩阵翻硬币
最近报名了蓝桥杯,应该是我学习编程以来的第一场正式比赛了,最近就刷了一下历年试题 这是其中比较有趣的题目,不过我的答案只有70%的分,想看正确代码可以看这里,没过是算法不太行,思路还是可以看看的。
问题描述
小明先把硬币摆成了一个 n 行 m 列的矩阵。
随后,小明对每一个硬币分别进行一次 Q 操作。对第x行第y列的硬币进行 Q 操作的定义:将所有第 ix 行,第 jy 列的硬币进行翻转。其中i和j为任意使操作可行的正整数,行号和列号都是从1开始。
当小明对所有硬币都进行了一次 Q 操作后,他发现了一个奇迹——所有硬币均为正面朝上。
小明想知道最开始有多少枚硬币是反面朝上的。于是,他向他的好朋友小M寻求帮助。
聪明的小M告诉小明,只需要对所有硬币再进行一次Q操作,即可恢复到最开始的状态。然而小明很懒,不愿意照做。于是小明希望你给出他更好的方法。帮他计算出答案。
题目分析
首先,对于一个硬币,如果翻了奇数次后正面向上,则说明它一开始是反面朝上的,偶数反之,故题目就是叫我们求对所有硬币进行了Q操作后有多少个硬币是翻了奇数次的。
然后,对Q操作进行分析。Q操作是
将所有第 i*x 行,第 j *y 列的硬币进行翻转。其中i和j为任意使操作可行的正整数,行号和列号都是从1开始
看上去有点像埃氏筛,盲猜和约数有关,然后按题意模拟一下n = 4 ,m = 6 的情况,用数组a[i][j],存一下结果,以下是模拟结果。
1 2 2 3 2 4
2 4 4 6 4 8
2 4 4 6 4 8
3 6 6 9 6 12
从模拟发现
1)a[i][j] = a[i][1] * a[1][j]
2)a[1][j] = j的约数个数,a[i][1] = i的约数个数
这个具体证明我就不写了。因为我们只要知道奇偶关系,所以可以考虑用1带表奇数,0带表偶数,将a[i][j] 里的值都用01替换掉后就将题目转换为求所有值的和了。因为a[i][j] = a[i][1] * a[1][j](将值替换后这规律依旧没变),所以全部数据的和可以用(a[1][1] +a[2][1] +…a[n][1])*(a[1][1]+a[1][2]+…a[1][m]) 表示。
因为用01替换了奇偶,所以(a[1][1] +a[2][1] +…a[n][1])就是a[i][1] 中奇数个数,结合发现1,问题转化为:求1到m中,有几个数的约数是奇数。一个数的约数是成对出现的,如果是k是j的约数则j/k也是,约数是奇数个,说明j存在一个约数k使k = j/k ,即j是平方数,所以问题又转换为:1到m有几个平方数,思考即可得答案为[sqrt(m)] (m的平方根向下取整)
所以最终答案就是m的平方根向下取整乘以n的平方根向下取整
本以为到这里就完了,没想到只是另一个开始,数据范围是1000位数,然后算平方根。。。你这是难为刚学编程的我啊,我用了一下高精度加二分,然后稍微优化,过了70%,以下是带码:
#include<iostream>
#include<string>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
vector<int>A,B,C,Mid2,D,E;
void print(vector<int>& A){
for(int i=A.size()-1;i>=0;i--)printf("%d",A[i]);
}
int com(vector<int>& A,vector<int>& B){//A>B 1 A<B -1 A==B 0
if(A.size()!=B.size())return A.size()>B.size()?1:-1;
for(int i=A.size()-1;i>=0;i--)if(A[i]!=B[i])return A[i]>B[i]?1:-1;
return 0;
}
//高精度除以低精度 A/b=C...r A>=0,b>0
vector<int>div(vector<int>& A,int b,int& r)
{
vector<int>C;
r=0;
for(int i=A.size()-1;i>=0;i--)
{
r=r*10+A[i];
C.push_back(r/b);
r%=b;
}
reverse(C.begin(),C.end());
while(C.size()>1&&C.back()==0)C.pop_back();
return C;
}
//高精度加法 C=A+B A>=0,B>=0
vector<int>add(vector<int>& A,vector<int>& B){
// printf("add ");print(A);printf(" ");print(B);printf(" ");
if(B.size()>A.size())return add(B,A);
vector<int>K;
for(int i=0,t=0;i<A.size()||t;i++){
if(i<A.size())t+=A[i];
if(i<B.size())t+=B[i];
K.push_back(t%10);
t=t/10;
}
// printf("ans=");print(K);printf("\n");
return K;
}
//高精度乘以低精度 C=A*b A>=0,b>=0
vector<int>mul(vector<int>& A,int b){
vector<int>C;
for(int i=0,t=0;i<A.size()||t;i++){
if(i<A.size())t+=A[i]*b;
C.push_back(t%10);
t/=10;
}
while(C.size()>1&&C.back()==0)C.pop_back();
return C;
}
//高精度乘以高精度 C=A*B A>=0,B>=0,A.size()==B.size()
vector<int>Mul(vector<int>&A,vector<int>&B)
{
vector<int>C,t1,t2;
C.push_back(0);
for(int i=A.size()-1;i>=0;i--)
{
t1=mul(C,10);
t2=mul(B,A[i]);
C=add(t1,t2);
}
while(C.size()>1&&C.back()==0)C.pop_back();
return C;
}
//高精度减法 C=A-B A>=B,A>=0,B>=0
vector<int>sub(vector<int>& A,vector<int>& B)
{
vector<int>C;
for(int i=0,t=0;i<A.size();i++){
t=A[i]-t;
if(i<B.size())t-=B[i];
C.push_back((t+10)%10);
if(t<0)t=1;
else t=0;
}
while(C.size()>1&&C.back()==0)C.pop_back();
return C;
}
vector<int>vsqrt(vector<int>& A){//A>1
vector<int>L,R,one,Mid;
int r;
if(A.size()>10){
int len=A.size()/2;
for(int i=1;i<len-1;i++)L.push_back(0);
L.push_back(1);
for(int i=1;i<=len+1;i++)R.push_back(0);
R.push_back(1);
}else{
L.push_back(0);
R=div(A,2,r);
}
// printf("%d %d\n",L.size(),R.size());
// print(L);
one.push_back(1);
while(com(R,L)==1){
vector<int>C=add(R,L);
Mid=div(C,2,r);
Mid2=Mul(Mid,Mid);
if(com(Mid2,A)==1)R=Mid;
else if(com(Mid2,A)==-1)L=add(Mid,one);
else break;
}
if(com(R,L)==1)return Mid;
C=sub(R,one);
Mid2=Mul(C,C);
while(com(Mid2,A)!=1){
C=add(C,one);
Mid2=Mul(C,C);
}
return sub(C,one);
}
int main(){//70% 暴力二分 运行超时
char ch;
vector<int>C,D,E;
while((ch=getchar())>='0'&&ch<='9')A.push_back(ch-'0');
reverse(A.begin(),A.end());
while((ch=getchar())>='0'&&ch<='9')B.push_back(ch-'0');
reverse(B.begin(),B.end());
C=vsqrt(A);
D=vsqrt(B);
// printf("\nC.size=%d %d\n",C.size(),D.size());
E=Mul(C,D);
print(E);
return 0;
}