矩阵翻硬币
这是蓝桥杯的一道练习题,题目如下:
问题描述
小明先把硬币摆成了一个 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;
对于10%的数据,n、m <= 10^1000(10的1000次方)。
思路:
对于坐标为(x,y)的硬币来说,当被翻转的次数为奇数时,它的朝向会被改变。被翻转的次数等于x的约数个数乘以y的约数个数。而翻转次数为奇数的充要条件时x和y的约数都是奇数,即x,y都是完全平方数。
对于矩阵nm,其中横纵坐标皆为完全平方数的硬币个数为:sqrt(n)sqrt(m)。
这道题的另一个难点是求超大整数的平方根。网上一个比较经典的算法是:
对于一个整数num,其位数为n,若n为奇数,则其平方根的位数length = (n+1)/2;若n为偶数,则其平方根的位数length = n/2。 我们从1000...000(有length-1个0)的最高位开始,若这个数的平方小于num,则将当前位的数值加1;若这个数的平方大于num,则将当前位的数值减1,并开始分析下一位。
计算平方时,免不了需要自己实现大数的乘法。同样是参考网上的例子,算法如下:
首先使用char数组存储整数,用int类型的数组ret暂时保存结果,整数的低位存在数组的低位。对输入的两个数组x和y,将其每一位x[i]和y[j]分别相乘,将数值添加到ret[i+y]中。
现在,ret的的每个元素可能是大于10的,对于每一个元素的数值,我们将个位数保存,将十位数进位。
ret[i+1] += ret[i] / 10;
ret[i] = ret[i] * 10;
最后,将int类型的数组转化为char类型并返回。
主要算法就是这些。
代码:
import java.util.Scanner;
public class Main {
public static void main(String[] args){
Scanner scan = new Scanner(System.in);
char[] n = null,m = null;
char[] temp = {1,2,3};
if(scan.hasNext())
n = scan.next().toCharArray();
if(scan.hasNext())
m = scan.next().toCharArray();
n = ReverseOrder(n);
m = ReverseOrder(m);
char[] ret = reverse(n,m);
int length = ret.length;
while(length>1 && ret[length-1] == '0')
length--;
for(int i=length-1; i>=0; i--)
System.out.print(ret[i]);
}
/**
* 使数组倒序排列
*/
private static char[] ReverseOrder(char[] a){
for(int i=0; i<(a.length/2); i++){
char temp = a[i];
a[i] = a[a.length-1-i];
a[a.length-1-i] = temp;
}
return a;
}
/**
* 按照游戏规则翻转矩阵中的硬币
*/
private static char[] reverse(char[] n, char[] m){
char[] sqrt_n = sqrt(n);
char[] sqrt_m = sqrt(m);
return mult(sqrt_n,sqrt_m);
}
/**
* 求大数的平方根(向下取整)
*/
private static char[] sqrt(char[] a){
int length = (a.length%2 == 1)? a.length/2+1 : a.length/2;
char[] ret = new char[length];
for(int i=0; i<length-1; i++)
ret[i]='0';
ret[length-1] = '1';
for(int i=length-1; i>=0; i--){
while(ret[i] <= '9'){
if(compare(a,mult(ret,ret)) != 1){
ret[i] = (char) (ret[i] - 1);
break;
}
if(ret[i] < '9')
ret[i] = (char) (ret[i] + 1);
else
break;
}
}
return ret;
}
/**
* 求大数的乘积
*/
private static char[] mult(char[] a, char[] b){
int[] number = new int[a.length+b.length];
for(int i=0;i<number.length;i++) number[i]=0;
int a_start = 0;
while(a[a_start]=='0')
a_start++;
int b_start = 0;
while(b[b_start]=='0')
b_start++;
for(int i=a_start; i<a.length; i++)
for(int j=b_start; j<b.length; j++){
number[i+j] += (a[i]-'0') * (b[j]-'0');
}
for(int k=a_start+b_start; k<number.length-1; k++){
number[k+1] += number[k]/10;
number[k] = number[k] % 10;
}
char[] ret;
if(number[number.length-1]==0)
ret = new char[number.length-1];
else
ret = new char[number.length];
for(int i=0; i<ret.length; i++){
ret[i] = (char) (number[i] + '0');
}
return ret;
}
/**
* 比较两个大数的大小
* @return
* -1 : 数组a的值更大;
* 0 : 两个数组的值相同;
* 1 : 数组a中的值更小;
*/
private static int compare(char[] a, char[] b){
if(a.length > b.length)
return 1;
if(a.length < b.length)
return -1;
for(int i = a.length-1; i>=0; i--){
if(a[i] > b[i])
return 1;
if(a[i] < b[i])
return -1;
}
return 0;
}
}