2020_java蓝桥_暑假训练_进制与整除

进制与整除训练

Strange_Donation

地产大亨Q先生临终的遗愿是:拿出100万元给X社区的居民抽奖,以稍慰藉心中愧疚。

麻烦的是,他有个很奇怪的要求:

  1. 100万元必须被正好分成若干份(不能剩余)。 每份必须是7的若干元。 比如:1元, 7元,49元,343元,…

  2. 相同金额的份数不能超过5份。

  3. 在满足上述要求的情况下,分成的份数越多越好!

请你帮忙计算一下,最多可以分为多少份?

分析

换个角度考虑,如果拿出1234567890元分给居民,每份必须是10的若干次方元,
且相同金额份数不超过9份,那么最多可以分为多少份? 那么只有1种情况,10元9份,100元8份,1000元7份,10000元6份…. 可以看到,这实际上是进制的问题,即题目要求的是以7进制形式表示后的数字,然后把每位数字相加即可求出份数。题意。

public class Strange_Donation {
	public static void main(String[] args) {
		System.out.println(donate(1000 * 1000, 7));
	}
	/**
	 * @param n
	 * @param m
	 * @return
	 */
	private static int donate(int n, int m) {
		String s = Integer.toString(n, m);
		int sum = 0;
		for(int i = 0; i < s.length(); i++) {
			sum += s.charAt(i) - '0';
		}
		return sum;
	}
}

Weight_weighing

用天平称重时,我们希望用尽可能少的砝码组合称出尽可能多的重量。如果只有5个砝码,重量分别是1,3,9,27,81则它们可以组合称出1到121之间任意整数重量(砝码允许放在左右两个盘中)。

本题目要求编程实现:对用户给定的重量,给出砝码组合方案。

例如:

用户输入:

5

程序输出:

9-3-1

用户输入:

19

程序输出:

27-9+1

要求程序输出的组合总是大数在前小数在后。

可以假设用户的输入的数字符合范围1~121。

分析

暴力解法

public class Weight_weighing {
	public static void main(String[] args) {
		Scanner input = new Scanner(System.in);
		int n = input.nextInt();
		weight(n);
		weight2(n);
		input.close();
	}
	private static void weight(int n) {
		int[] sign = {-1, 0, 1};
		StringBuilder str = new StringBuilder();
		for(int a : sign) {
			for(int b : sign) {
				for(int c : sign) {
					for(int d : sign) {
						for(int e: sign) {
							int i = a * 1;
							int j = b * 3;
							int k = c * 9;
							int l = d * 27;
							int m = e * 81;
							if(i + j + k + l + m == n) {
								str.append(m != 0 ? (m > 0 ? "+" + m : m) : "");
								str.append(l != 0 ? (l > 0 ? "+" + l : l) : "");
								str.append(k != 0 ? (k > 0 ? "+" + k : k) : "");
								str.append(j != 0 ? (j > 0 ? "+" + j : j) : "");
								str.append(i != 0 ? (i > 0 ? "+" + i : i) : "");
								str.deleteCharAt(0);
								System.out.println(str);
								return;
							}
						}
					}
				}
			}
		}
	}
}

解法二:利用进制

public class Weight_weighing {
	public static void main(String[] args) {
		Scanner input = new Scanner(System.in);
		int n = input.nextInt();
		weight2(n);
		input.close();
	}
	private static void weight2(int n) {
		//转化为三进制
		String x = Integer.toString(n, 3);// 1000=27三进制 举例25 等于2 2 1
		// 翻转后转成字符数组 为什么翻转:好进位1 3 9
		char[] arr = new StringBuffer(x).reverse().toString().toCharArray();// 翻转变成了1 2 2
		List<Integer> list = new ArrayList<>();// 变成了1 0 -1 1 从右到左插入
		
		for(int i = 0; i < arr.length; i++) {// 数组 1 2 2
			if(arr[i] == '2') {
				list.add(0, -1);// -1插在开头
				if(i == arr.length - 1) {
					list.add(0, 1);// 最后一个字符,进位 变成3
				}else {
					++arr[i + 1];
				}
			}else if(arr[i] == '3') {
				list.add(0, 0);// 插入0 >>>>1 0 -1 1
				// 更高位
				if(i == arr.length - 1) {
					list.add(0, 1);
				}else {
					++arr[i + 1];
				}
			}else {
				list.add(0, arr[i] - '0');// 只有1 和0的情况 arr[i]-'0'>>> 转化成数字
			}
		}
		StringBuilder str = new StringBuilder();//恢复成十进制
		for(int i = 0; i < list.size(); i++) {
			if(list.get(i) == 1) {
				str.append("+").append((int)Math.pow(3, list.size() - i - 1));
			}
			if(list.get(i) == -1) {
				str.append("-").append((int)Math.pow(3, list.size() - i - 1));
			}
		}
	System.out.println(str.substring(1));
	}
}

Nim_Pile

有3堆硬币,分别是3,4,5

二人轮流取硬币。

每人每次只能从某一堆上取任意数量。

不能弃权。

取到最后一枚硬币的为赢家。

求先取硬币一方有无必胜的招法。

分析

这个题有固定的解法,用二进制模2的加法/异或。
具体意思是:将所有堆的数目进行模2加法/异或,如果加起来全为0,那么将要抓堆的这个人就必输了;如果不全为0,那么这个人通过计算抓堆的数量就会让对方输。
举例来说:一共4堆:2,5,12,14
二进制对应:
0010
0101
1100
1110

0101
对每个堆的数目进行异或后结果不为0,所以将要抓堆的这个人不会输,那么他如何让对方输呢?
就是在他抓取一次后给对方留的堆的数目异或起来为0,由0101->0000,将左起第2位和第4位变0,分析造成位结果为1的原因,把某一堆堆数量的对应位取反就可以了(0->1or1->0)
由异或运算性质,0异或任何数,其结果=任何数,1异或任何数,其结果=把该数取反。
这个异或运算的结果恰好就是我们想要达到的效果。所以我们可以将所有数异或运算后的结果再与某堆数目进行异或,得到的结果就是该堆剩下的数目。

public class Nim_Pile {
	public static void main(String[] args) {
		int[] coin = {3, 4, 5};
		method(coin);
	}
	private static void method(int[] co) {
		int sum = 0;
		for(int i = 0; i < co.length; i++) {//按位异或后的结果 
			sum ^= co[i];
		}
		if(sum == 0) {
			System.out.println("必输");
		}
		for(int i = 0; i < co.length; i++) {
			int x = sum ^ co[i]; //x:在取了第i堆后剩下的 
			if(x < co[i]) {
				System.out.println(co[i] + "->" + x);//原先第i堆的个数->取后剩下的个数 
			}
		}
	}
}

The_Least_Multiple_of_Number

如果两个数很大,怎样求最大公约数,最小公倍数?

如果是n个数呢?比如1000个数的最小公倍数

分析

求解n个数的最小公倍数一般有两种做法:

1.分解质因数法:先把这几个数分解质因数,再把它们一切公有的质因数和其中几个数公有的质因数以及每个数的独有的质因数全部连乘起来,所得的积就是它们的最小公倍数.

12 15 8 9

12=223

15=3*5

8=222

9=3*3

其中12和8公有两个2,而12,15,9则公有质因子3,剩下的15独自有一个5,8独自有一个2,9独自有一个3

因此:最小公倍数为22352*3=360

这种方法个人觉得适合人去算,并不适合计算机去计算。计算机更适合采用第二种方法。

2.公式法两两运用

假设现在要求最小公倍数的两个数为x,y,他们的最大公约数为p,最小公倍数为q。则xy=pq,也就是说只要求得两个数的最大公约数就可求得这两个数的最小公倍数。

但是题目中要求的是n个数的 最小公倍数,这里只需要用最小公倍数代替原来的两个数即可。

例如:12 15 8 9

第一步:求得12和15的最小公倍数为60

第二部:求得60和8的最小公倍数为120

第三步:求得120和9的最小公倍数为360

所以,原问题转换为求两个数的最大公约数。最大公约数有几种求法,第一种对于小整数的辗转相除,大整数用辗转相减,还有优化版本的辗转相减法,这里给定数字范围为int因此直接采用辗转相除法。

辗转相除求最大公约数O(lgn)
递归写法:

int gcd(int a,int b){
	if(b==0)
		return a;
	else
		return gcd(b,a%b);
}

非递归写法:

int gcd(int a,int b){
	int c;
	while(b){
		c=a;
		a=b;
		b=c%b
	}
	return a;
}

利用xy=qp得到最小公倍数q=x*y/p;

public class The_Least_Multiple_of_Number {
	public static void main(String[] args) {
		Scanner input = new Scanner(System.in);
		int n = input.nextInt();
		int[] arr = new int[n + 1];
		for(int i = 1; i <= n; i++) {
			arr[i] = input.nextInt();
			if(i != 1) {
				arr[i] = arr[i - 1] / gcb(arr[i - 1], arr[i]) * arr[i];				
			}
		}
		System.out.println(arr[n]);
		input.close();
	}
	private static int gcb(int a, int b) {
		if(b == 0) {
			return a;
		}else {
			return gcb(b, a % b);			
		}
	}
}

One_step_away

从昏迷中醒来,小明发现自己被关在X星球的废矿车里。矿车停在平直的废弃的轨道上。他的面前是两个按钮,分别写着“F”和“B”。 小明突然记起来,这两个按钮可以控制矿车在轨道上前进和后退。按F,会前进97米。按B会后退127米。透过昏暗的灯光,小明看到自己前方1米远正好有个监控探头。他必须设法使得矿车正好停在摄像头的下方,才有机会争取同伴的援助。或许,通过多次操作F和B可以办到。 矿车上的动力已经不太足,黄色的警示灯在默默闪烁…每次进行 F 或 B 操作都会消耗一定的能量。小明飞快地计算,至少要多少次操作,才能把矿车准确地停在前方1米远的地方。 请填写为了达成目标,最少需要操作的次数。

暴力解法

public class One_step_away {
	public static void main(String[] args) {
		operation(97, 127);
	}
	/**
	 * 暴力解法
	 * @param n
	 * @param m
	 */
	private static void operation(int n, int m) {
		for(int t = 0; ; t++) {
			for(int x  = 0; x < t; x++) {
				int y = t - x;
				if(n * x - m * y == 1) {
					System.out.println("走" + n + " " + x  + "步" + m +" " + y + "步");
					return;
				}
			}
		}
	}
}

解法二:坐标

将轨道视为一条坐标轴,探头视为原点,由车指向探头方向为正方向,则车的初始坐标为-1,当车的坐标<127米时,车前进一次,坐标+97,当车的坐标>=127时,车后退一次,坐标 -127,

public class One_step_away {
	public static void main(String[] args) {
		operation1(97, 127);
	}
	private static void operation1(int n, int m) {
		int x = -1;
		for(int i = 1; ; i++) {
			x += n;//往前走一次
			if(x >= m) {//如果车的位置距离探头超过127米,就往后回一次
				x -= m;
				i++;
			}
			if(x == 0) {
				//正好在探头下
				System.out.println(i);
				return;
			}
			
		}
	}
}

解法三:扩展欧几里德算法

详情可查看扩展欧几里德算法

当我们算出 97x+127y=gcd(97,127)的第一组解(x0,y0)后;

我们可以得到 97x+127y=1的第一组解:

x=x0*1/gcd(97,127);

y=y0*1/gcd(97,127);

然后我们输出 abs(x)+ abs(y)即可。

public class One_step_away {
	public static void main(String[] args) {
		int byue = operation2(97, 127);//调用后x,y作为 97x+127y=gcd(97,127)的第一组解
		System.out.println(byue);
		System.out.println(Math.abs(x * 1 / byue) + " " + Math.abs(y * 1 / byue));
	}
	private static int x;
	private static int y;
	private static int operation2(int n, int m) {
		int d;
		if(m == 0){
			x = 1;
			y = 0;
			return n;
		}
		else {
			d = operation2(m, n % m);
			int temp = x;
			x = y;
			y = temp - (n / m) * y;
			return d;
		}
	}
}

Sum_of_Fractions

如果求 1/2 + 1/3 + 1/4 + 1/5 + 1/6 + … + 1/100 = ? 要求绝对精确,不能有误差。

分析

常规思路,要求绝对精确,所以利用API

public class Sum_of_Fractions {
	public static void main(String[] args) {
		BigDecimal s = new BigDecimal(0);
		Scanner input = new Scanner(System.in);
		int n = input.nextInt();
		for(int i = 1; i < n; i++) {
			s = s.add(new BigDecimal(1.0 / i));
		}
		System.out.println(s);
		input.close();
	}
}

Nth_Prime

第1个素数是2,第2个素数是3,…

求第100002(十万零二)个素数

利用API

public class Nth_Prime {
	public static void main(String[] args) {
		Scanner input = new Scanner(System.in);
		int n = input.nextInt();
		n_prime(n);
		input.close();
	}
	/**
	 * 利用API
	 * @param n
	 */
	private static void n_prime(int n) {
		BigInteger in = new BigInteger("2");
		
		for(int i = 2; i <= n; i++) {
			in = in.nextProbablePrime();
		}
		System.out.println(in);
	}
}

常规方法,编写一个判断素数的方法,并将素数储存

public class Nth_Prime {
	public static void main(String[] args) {
		Scanner input = new Scanner(System.in);
		int n = input.nextInt();
		n_prime1(n);
		input.close();
	}
	private static void n_prime1(int n) {
		List<Long> list = new ArrayList<>();
		
		for(long i = 2; list.size() < n; i++) {
			if(prime(i)) {
				list.add(i);
			}
		}
		System.out.println(list.get(n - 1));
	}
	private static boolean prime(long n) {
		if(n < 2) {
			return false;
		}
		if(n == 2) {
			return true;
		}
		if(n % 2 == 0) {
			return false;
		}
		for(int i = 3; i * i <= n;i += 2) {
			if(n % i == 0) {
				return false;
			}
		}
		return true;
	}
}

public class Nth_Prime {
	public static void main(String[] args) {
		Scanner input = new Scanner(System.in);
		int n = input.nextInt();
		n_prime2(n);
		input.close();
	}
	private static void n_prime2(int n) {
		int arr[] = new int[n + 1];
		for(int i = 2; i <= n; i++) {
			arr[i] = 1;
		}
		for(int i = 2; i * i <= n; i++) {//可以只用根号n之前的数字去筛选
			if(arr[i] == 1) { //不加判断也是可以的,增加复杂度了,我们只需要在我们所认为的“素数”中筛选就可以了, 每个合数必有质因子
				for(int j = i * i; j <= n; j += i) { //比如j = i * k(k < i),那么j肯定被k筛掉了
					arr[j] = 0;
				}
			}
		}
		int k = 1;
		for(int i = 2; i <= n; i++) {
			if(arr[i] == 1) {
				if(k == 100002) {
					System.out.println("->"+i);
					return;
				}
				k++;
			}
		}
	}
}

最后,不经历风雨,怎能在计算机的大山之顶看见彩虹呢! 无论怎样,相信明天一定会更好!!!!!

你可能感兴趣的:(java,算法设计)