蓝桥杯试题:矩阵翻硬币

矩阵翻硬币

 这是蓝桥杯的一道练习题,题目如下:

问题描述
  小明先把硬币摆成了一个 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;
	}
}
posted @ 2016-05-24 10:48  xuyue1160  阅读(264)  评论(0编辑  收藏  举报