一、题目名称:

Hankson 的趣味题

二、题目内容:

Hanks博士是BT(Bio-Tech,生物技术)领域的知名专家,他的儿子名叫Hankson。现在,刚刚放学回家的Hankson正在思考一个有趣的问题。

今天在课堂上,老师讲解了如何求两个正整数c1和c2的最大公约数和最小公倍数。现在Hankson认为自己已经熟练地掌握了这些知识,他开始思考一个“求公约数”和“求公倍数”之类问题的“逆问题”,这个问题是这样的:已知正整数a0,a1,b0,b1,设某未知正整数x满足:

1、  x和a0的最大公约数是a1;

2、  x和b0的最小公倍数是b1。

Hankson的“逆问题”就是求出满足条件的正整数x。但稍加思索之后,他发现这样的x并不唯一,甚至可能不存在。因此他转而开始考虑如何求解满足条件的x的个数。

三、算法设计:

由最大公约数和最小公倍数的关系可知①式:

第二章作业说明

由已知②③式:

第二章作业说明

第二章作业说明

式中a0,a1,b0,b1均为用户自己输入,故可以视为已知。

由②③式联立,运用性质①经过变形可得式④:

第二章作业说明

联立以上①②③④各式并整理可得式⑤:

第二章作业说明

由此,可以初步得到x的条件:既满足公式②又满足公式⑤,只需将x从1开始枚举到b1即可。

但是,由上次的作业可以知道,运用辗转相除法计算最大公约数时,运行的时间会随数据规模的增大而逐渐增加。因此对该算法进行优化。

由最大公约数的性质得⑥式:

第二章作业说明

因此可以将⑤式化简为⑦:

第二章作业说明

也可以将②式化简为⑧:

第二章作业说明

这样就将求最大公约数时的数据进一步缩小。同时由③式可知,x必为b1的因子,因此只需将枚举范围缩小至1到sqrt(b1)即可,但应该注意,当x为b1的因子时,b1/x也为b1的因子。

对于求最大公约数,采用辗转相除法即可。

第二章作业说明
图 1 辗转相除法流程图

四、调试截图:

  • 输入数据调试

第二章作业说明
图 2 输入数据调试

 

  • 计数器变化调试

第二章作业说明
图 3 计数器变化调试

 

五、测试结果:

第二章作业说明
图 4 测试用例的运行结果

 

六、遇到的困难及解决方案:

本次题目中共有5个变量,但其中4个是由输入得到,只有一个待求,不难让人想到用枚举法暴力**。但是在真正的比赛环境中,往往会有时间限制,所以暴力法只能解决部分问题,而不能拿到满分。因此必须在暴力枚举的基础上进行优化。而这其中遇到的最大的困难应该是数学上的证明,通过找gcd和lcm之间的关系和其各自的性质,然后结合题目条件加以推导,尽可能的缩小传入gcd()函数的两个参数的值,容易找到一些判断x的优化方法,这使得程序的运行效率得到很大的提升。当然,数学公式的编辑也较为繁琐,上文采用的是在线公式编辑网站,格式为png格式的图片格式。

七、源代码

/**  
 * All rights Reserved, Designed By YanCX
 * @Title:  Hankson.java   
 * @Package chaptertwo   
 * @Description:    TODO   
 * @author: YanCX     
 * @date:   2019年3月23日 下午6:47:55   
 * @version V1.0 
 * @Copyright: 2019  All rights reserved. 
 */
package chaptertwo;

import java.util.Scanner;

/**   
 * @ClassName:  Hankson   
 * @Description: 
 * @author: YanCX 
 * @date:   2019年3月23日 下午6:47:55   
 */
public class Hankson {

	/**   
	 * @Title: main   
	 * @Description: TODO(这里用一句话描述这个方法的作用)   
	 * @param: @param args      
	 * @return: void      
	 * @throws   
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub

		int n = 0; // 次数
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();

		for (int j = 0; j < n; j++) {

			int a0 = 0, a1 = 0, b0 = 0, b1 = 0;

			// 读入数据
			a0 = sc.nextInt();
			a1 = sc.nextInt();
			b0 = sc.nextInt();
			b1 = sc.nextInt();
			
			int cnt = 0; // 满足条件的x的数目
			int sqrt = (int) Math.sqrt(b1);
			
			// 暴力枚举
			for (int i = 1; i <= sqrt; i++) {
				
				// i必为b1的因子
				if (b1 % i == 0) {
					if (i % a1 == 0 && gcd(i / a1, a0 / a1) == 1 && gcd(b1 / b0, b1 / i) == 1)
						cnt++;
					int y = b1 / i; // 得到另一个因子
					if (i == y)
						continue;
					if (y % a1 == 0 && gcd(y / a1, a0 / a1) == 1 && gcd(b1 / b0, b1 / y) == 1)
						cnt++;
				}
			}
			System.out.println(cnt);
		}
		sc.close();
	}

	// 最大公约数
	public static int gcd(int a, int b) {
		int max, min;

		max = (a > b) ? a : b;
		min = (a < b) ? a : b;

		if (max % min != 0) {
			return gcd(min, max % min);
		} else
			return min;

	}

	// 最小公倍数
	public static int lcm(int a, int b) {
		return a * b / gcd(a, b);
	}
}

 

相关文章: