蓝桥杯第九届JavaB组——适合数学基础欠佳+编程欠佳的同学参考

一、第几天

2000年的1月1日,是那一年的第1天。 那么,2000年的5月4日,是那一年的第几天?
注意:需要提交的是一个整数,不要填写任何多余内容。

解法一:
1月:31
2月:是否闰年——>是:29
3月:31
4月:30
5月:4
累加:31+29+31+30+4=125

解法二:求出相差多少天,最后+1

public class 第几天 {
	public static void main(String[] args) throws ParseException {
		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
		Date date1 = dateFormat.parse("2000-01-01");
		Date date2 = dateFormat.parse("2000-05-04");
		long days = (date2.getTime() - date1.getTime()) / 1000 / 60 / 60 / 24;
		System.out.println("两个时间相距:" + days + "天");
	}
}

在这里插入图片描述
124+1=125!!
记住不要搞错了!!

二、方格计数

如图p1.png所示,在二维平面上有无数个1x1的小方格。 我们以某个小方格的一个顶点为圆心画一个半径为1000的圆。
你能计算出这个圆里有多少个完整的小方格吗?
注意:需要提交的是一个整数,不要填写任何多余内容。
在这里插入图片描述

有这样想过的朋友举爪——认为就是求中间粉色正方形的面积,其边长为(r *2) -2
在这里插入图片描述

然而,题目就是故意这样的吧!!!
我这个思路没有考虑全面,这些方块不一定能构成上面粉色区域的正方形,还有下面这张图的红色部分!!!我的思路过于简单
在这里插入图片描述
所以比较好的解法应该是:
求出一个象限的方块个数,然后*4 即可求出整个圆里面的方块。
在这里插入图片描述
若一个象限中,x和y 构成的矩形如果在r *r 构成的矩形中,那么说明存在有完整的小方块,但是为什么能取到 = 号我就没有想到了
在这里插入图片描述

public class 方格计数 {
	public static void main(String[] args) {
		int r = 1000;
		int ans = 0;
		for (int x = 1; x <= r; x++) {
			for (int y = 1; y <= r; y++) {
				if (x * x + y * y <= r * r) {
					ans++;
				}
			}
		}
		System.out.println(ans * 4);
	}
}
// 更优化的方法
		int y = r;
		int cnt = 0;
		for (int x = 1; x <= r; x++) {
			while (x * x + y * y > r * r && y > 0) {
				y--;
			}
			cnt += y;// 出来的y都是合法的
		}
		System.out.println(cnt * 4);

或者暴力更好理解——只要四个点都在圆内,那就构成一个

public static void func(int r) {
		int ans = 0;
		for (int i = -1000; i <= r; i++) {
			for (int j = -1000; j <= r; j++) {
				// 相邻4点都在圆内,则可以构成
				if (check(r, i, j) && check(r, i + 1, j + 1) && check(r, i + 1, j) && check(r, i, j + 1)) {
					ans++;
				}
			}
		}
		System.out.println(ans);

	}

	public static boolean check(int r, int x, int y) {
		// 坐标合法
		if (x >= -r && x <= r && y >= -r && y <= r) {
			if (x * x + y * y <= r * r) {// 点在圆内
				return true;
			}
		}
		return false;
	}

取= 号是因为:
在这里插入图片描述

在这里插入图片描述

这道题,涉及数学的思维,我太菜了,根本理解不了!!一点都理解不了!!啊啊啊啊啊

三、复数幂

设 i 为虚数单位。对于任意正整数n,(2+3i) ^ n 的实部和虚部都是整数。
求 (2+3i)^123456 等于多少?—— 即(2+3i)的123456次幂,这个数字很大,要求精确表示。

答案写成 “实部±虚部i” 的形式,实部和虚部都是整数(不能用科学计数法表示),中间任何地方都不加空格,实部为正时前面不加正号。
(2+3i)^2 写成: -5+12i,
(2+3i)^5 的写成: 122-597i
注意:需要提交的是一个很庞大的复数,不要填写任何多余内容。

复数运算公式:
( a + bi ) * ( c + di ) = ( a * c - b * d ) + ( a * d + b * c )i
最重要的是:i^2 =-1
public class 复数幂 {
	public static void main(String[] args) throws FileNotFoundException {
		BigInteger a = BigInteger.valueOf(2);
		BigInteger two = BigInteger.valueOf(2);
		BigInteger b = BigInteger.valueOf(3);
		BigInteger three = BigInteger.valueOf(3);
		BigInteger aa = null;
		BigInteger bb = null;
		for (int i = 1; i < 123456; i++) {
			aa = a.multiply(two).subtract(b.multiply(three));// a*2-b*3
			bb = a.multiply(three).add(b.multiply(two));
			a = aa;
			b = bb;
		}
		System.setOut(new PrintStream(new File("E:\\蓝桥杯训练\\题目文件\\复数幂.txt")));
		// (b.compareTo(BigInteger.ZERO) < 0 ? "-" : "+") 本身b就有正负号,可以不用讨论
		System.out.println(a.toString() + b.toString() + "i");
	}
}

答案:
a、 b 都是负的!太长了,电脑都卡了,就不粘贴了
在这里插入图片描述
在这里插入图片描述

四、测试次数

打扰了:不会做
leetcode类似的题:鸡蛋掉落
在这里插入图片描述
看一下大佬的:

public class _04_测试次数 {
 
	public static void main(String[] args) {
		for(int i=0;i<10;i++)
			for(int j=0;j<10000;j++)
				memo[i][j] = 99999999;//找最小,初始化INF
		System.out.println(f(3,1000));
	}
	
	static int[][] memo = new int[10][10005];
	
	static int f(int n,int m) {
		if(n==1)
			return m;
		if(m==1 || m==2)
			return 1;
			
		if(memo[n][m]!=99999999)
			return memo[n][m];
		
		for(int i=1;i<=m;i++) {
			memo[n][m] = Math.min(memo[n][m], 1+Math.max(f(n,m-i),f(n-1,i)));
		}
		return memo[n][m];
	}
 
}

五、快速排序

以下代码可以从数组a[]中找出第k小的元素。
它使用了类似快速排序中的分治算法,期望时间复杂度是O(N)的。
请仔细阅读分析源码,填写划线部分缺失的内容。

import java.util.Random;
public class Main{
	public static int quickSelect(int a[], int l, int r, int k) {
		Random rand = new Random();
		int p = rand.nextInt(r - l + 1) + l;
		int x = a[p];
		int tmp = a[p]; a[p] = a[r]; a[r] = tmp;
		int i = l, j = r;
		while(i < j) {
                	while(i < j && a[i] < x) i++;
                	if(i < j) {
                        	a[j] = a[i];
                        	j--;
                	}
                	while(i < j && a[j] > x) j--;
                	if(i < j) {
                        	a[i] = a[j];
                        	i++;
                	}
        	}
        	a[i] = x;
        	p = i;
        	if(i - l + 1 == k) return a[i];
        	if(i - l + 1 < k) return quickSelect( _________________________________ ); //填空
        	else return quickSelect(a, l, i - 1, k);	
	}
	public static void main(String args[]) {
		int [] a = {1, 4, 2, 8, 5, 7};
		System.out.println(quickSelect(a, 0, 5, 4));
	}
}

注意:只提交划线部分缺少的代码,不要抄写任何已经存在的代码或符号。

我的答案:

			if (i - l + 1 < k)
				return quickSelect(a, i + 1, r, k); // 填空
			else
				return quickSelect(a, l, i - 1, k);

我错了,我是按照经典快排的思想,但是这道题应该是随机快排的思想

参考:什么是快速排序
第九届蓝桥杯快速排序

public class 快速排序 {
	// 以下代码可以从数组a[]中找出第k小的元素。
	public static int quickSelect(int a[], int l, int r, int k) {
		// 1、随机选择一个元素作为基准元素(此时选择的基准元素为x)
		Random rand = new Random();
		int p = rand.nextInt(r - l + 1) + l;
		int x = a[p];
		// 这里令我们随机选择的元素a[p]与数组最右端的元素a[r]互换
		int tmp = a[p];
		a[p] = a[r];
		a[r] = tmp;
		// 2、相当于设置两个指针i和j,i为左指针,j为右指针,它们分别指向数组最左和最右的元素
		int i = l, j = r;
		while (i < j) {
			// 3、将左指针指向的元素与基准元素相比较,如果左指针指向的元素小于基准元素,则左指针向右移动一位
			while (i < j && a[i] < x)
				i++;
			// 4、通过上一步的移动,左指针左边的元素全部比基准元素小,
			// 令数组最右的元素(此时的数组最右的元素等于基准元素x)等于此时左指针指向的元素,
			// 右指针向左移动一位,此时的a[j]大于等于x
			if (i < j) {
				a[j] = a[i];
				j--;
			}

			// 5、将右指针指向的元素与基准元素相比较,如果右指针指向的元素大于基准元素,则右指针向左移动一位
			while (i < j && a[j] > x)
				j--;
			// 6、通过上一步的移动,右指针右边的元素全部比基准元素大,
			// 令左指针指向的元素等于此时右指针指向的元素,左指针向右移动一位,此时的a[i]小于等于x
			if (i < j) {
				a[i] = a[j];
				i++;
			}
		}
		// 7、通过步骤3、4、5、6后,可以确定a[i]的位置就是我们选择的基准元素排序后应该所在的位置
		a[i] = x;
		// 8、令基准元素位置的索引i等于p
		p = i;
		// 9、通过算式i - l + 1得到的是在数组中第几小的元素,若它等于k,则得到题目要求的数组a[]中第k小的元素
		if (i - l + 1 == k)
			return a[i];
		// 若小于k,即我们所求的元素在i的右边,此时左指针指向的元素为a[p+1],右指针指向的元素依旧是a[r],
		// 所求第k小的元素变为第k - (i - l + 1)小
		if (i - l + 1 < k)
			return quickSelect(a, i + 1, r, k - (i - l + 1)); // 填空
		else
			// 若大于k,即我们所求的元素在i的左边,此时左指针指向的元素为认为a[l],右指针指向的元素为a[i-1],仍旧是求第k小的元素
			return quickSelect(a, l, i - 1, k);
	}
	public static void main(String args[]) {
		int[] a = { 1, 4, 2, 8, 5, 7 };
		System.out.println(quickSelect(a, 0, 5, 4));
	}

}

答案应该是:

a, i + 1, r, k - (i - l + 1)

六、递增三元组

给定三个整数数组 A = [A1, A2, … AN], B = [B1, B2, … BN], C = [C1, C2, … CN], 请你统计有多少个三元组(i, j, k) 满足:

  1. 1 <= i, j, k <= N
  2. Ai < Bj < Ck

【输入格式】
第一行包含一个整数N。 第二行包含N个整数A1, A2, … AN。 第三行包含N个整数B1, B2, … BN。 第四行包含N个整数C1, C2, … CN。
对于30%的数据,1 <= N <= 100
对于60%的数据,1 <= N <= 1000
对于100%的数据,1 <= N <= 100000 ;0 <= Ai, Bi, Ci <= 100000
【输出格式】一个整数表示答案

【输入样例】
3
1 1 1
2 2 2
3 3 3
【输出样例】
27

暴力肯定过不了,所以要想办法优化
在这里插入图片描述

  1. 对三个数组都排序;
  2. 以b数组为基准,从b[0]开始,在a数组中找到比b[0]大的第一个元素的下标p,那么可以得到在a数组中比b[0]小的元素个数为p;(下标:p-1-0+1)
  3. 在c数组中找到比b[0]大的第一个元素的下标q,那么比b[0]大的元素有N-q个(下标:N-1-q+1)
  4. 同理,进一步的,b[1]比b[0]大,所以可以直接在b[0]得到的结果上,继续在a数组中找比b[1]大的第一个元素的下标p(继续用p),在c数组中找到比b[1]大的第一个元素的下标q。。。
  5. cnt+= p * (N - q) * 1L;//数据很大!要用long!

细节:一定要先判断下标是否合法,再进行数组元素的比较
用两种方法来做,前面部分都一样:

		Scanner sc = new Scanner(System.in);
		int N = sc.nextInt();
		int[] a = new int[N];
		int[] b = new int[N];
		int[] c = new int[N];
		for (int i = 0; i < N; i++) {
			a[i] = sc.nextInt();
		}
		for (int i = 0; i < N; i++) {
			b[i] = sc.nextInt();
		}
		for (int i = 0; i < N; i++) {
			c[i] = sc.nextInt();
		}
		Arrays.sort(a);
		Arrays.sort(b);
		Arrays.sort(c);

解法一:上述的方法

		long cnt = 0;
		int p = 0;
		int q = 0;
		for (int i = 0; i < N; i++) {
			while (p < N && a[p] < b[i]) {// 下标应先合法,在a中找到比b[i]大的第一个元素的下标
				p++;
			}
			while (q < N && b[i] > c[q]) {// 在c中找到比b[i]大的第一个元素的下标
				q++;
			}
			cnt += 1L * p * (N - q);// 数据很大,用long
		}
		System.out.println(cnt);

细节:cnt += 1L * p * (N - q);// 数据很大,用long

解法二:二分

	long res = 0;
	for (int i = 0; i < N; i++) {
		// 在b数组中找到比a[i]恰好大的第一个元素的下标,那么这个下标及其之后都比a[i]大
		int temp = binarySearch(b, a[i]);
		if (temp != -1) {
			// 在c中找到恰好比b[temp]大的第一个元素的下标,那么这个下标及其之后都比b[temp]大
			int temp2 = binarySearch(c, b[temp]);
			if (temp2 != -1) {
				res += (N - temp) * (N - temp2);
			}
		}
	
	}
	System.out.println(res);
	}

	public static int binarySearch(int[] arr, int target) {
		int left = 0, right = arr.length;
		int ans = -1;
		while (left < right) {
			int mid = (right - left) / 2 + left;
			if (arr[mid] > target) {
				right = mid;// 找到恰好>target的第一个下标
				ans = mid;// 记录当前比target大的下标
			} else if (arr[mid] <= target) {
				left = mid + 1;
			}
		}
		return ans;// 找不到就返回-1,说明没有target
	}

细节:分析数据的范围,判断用什么类型的变量

七、螺旋折线

如图p1.pgn所示的螺旋折线经过平面上所有整点恰好一次。
对于整点(X, Y),我们定义它到原点的距离dis(X, Y)是从原点到(X, Y)的螺旋折线段的长度。
例如dis(0, 1)=3, dis(-2, -1)=9
给出整点坐标(X, Y),你能计算出dis(X, Y)吗?

【输入格式】
X和Y
对于40%的数据,-1000 <= X, Y <= 1000
对于70%的数据,-100000 <= X, Y <= 100000
对于100%的数据, -1000000000 <= X, Y <= 1000000000
【输出格式】输出dis(X, Y)

【输入样例】
0 1
【输出样例】
3
在这里插入图片描述

分析:数据范围很大,所以用long。

解法一、暴力,过不了

static public long stepFun(int x, int y) {
		int level = 0;
		int row = 0, col = 0;
		long step = 0;
		Out: while (row != x || col != y) {
			// 向左
			for (int i = 0; i <= level; i++) {
				if (row == x && col == y) {
					break Out;
				}
				step++;
				row--;
			}
			// 向上
			for (int i = 0; i <= level; i++) {
				if (row == x && col == y) {
					break Out;
				}
				step++;
				col++;
			}
			level++;
			// 向右
			for (int i = 0; i <= level; i++) {
				if (row == x && col == y) {
					break Out;
				}
				step++;
				row++;

			}
			// 向下
			for (int i = 0; i <= level; i++) {
				if (row == x && col == y) {
					break Out;
				}
				step++;
				col--;
			}
			level++;
		}
		return step;
	}

解法二:数学,找规律
在这里插入图片描述
在这里插入图片描述
划分一条线,以右下角的点为基准:

  1. 如果在右上部分,那么每一层到达右下角的最大距离呈现以4为公差的等差数列:

a0=4
an=a0+(n-1)*d

  1. 如果在左下部分,那么每一层到达右下角的最大距离呈现以4为公差的等差数列:

a0=2
an=a0+(n-1)*d

在这里插入图片描述
把上面的拆解成四个方向:

在这里插入图片描述
其实我这里还少考虑了横行的坐标轴,修改“下”部分:

(y <= 0 && x >= y - 1 && x <= -y)

好,我们现在知道了方向,现在我们思考

  1. 如何找到该坐标位于哪一层?
  2. 如何计算该坐标与右下角基准点的距离?

搞了半天我也没有做对,算了我没有这个能力,我不做了,拜拜

public class 螺旋折线2 {
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		long x = scanner.nextLong();// 传入的数据也很大!
		long y = scanner.nextLong();
		long distance = 0;// 与第几圈的(x,-x)的距离
		long n = 0;// 第几圈
		if (y > 0 && Math.abs(x) <= y) {
			n = y;
			distance = y - x + 2 * y;
		} else if (x > 0 && Math.abs(y) <= x) {
			n = x;
			distance = x + y;// y<0,如果是右部分的上部分,早在上面的if中就被拦下了
		} else if (y <= 0 && x >= y - 1 && x <= -y) {
			n = -y;
			distance = -(-y - x);//x+y
		} else if (x < 0 && y >= x + 1 && y <= -x) {
			n = -x - 1;
			distance = -(y - x - 1 - 2 * x - 1);
		}
		System.out.println(sum(1L, 2 * n, 1) * 2 - distance);
	}

	// 等差数列求和
	public static long sum(long a0, long n, int d) {
		return (2 * a0 + (n - 1) * d) * n / 2;

	}
}

八、日志统计

小明维护着一个程序员论坛。现在他收集了一份"点赞"日志,日志共有N行。
其中每一行的格式是:ts id ——表示在ts时刻编号id的帖子收到一个"赞"。
现在小明想统计有哪些帖子曾经是"热帖"。如果一个帖子曾在任意一个长度为D的时间段内收到不少于K个赞,小明就认为这个帖子曾是"热帖"。
具体来说,如果存在某个时刻T满足该帖在[T, T+D)这段时间内(注意是左闭右开区间)收到不少于K个赞,该帖就曾是"热帖"。 给定日志,请你帮助小明统计出所有曾是"热帖"的帖子编号。

【输入格式】
第一行包含三个整数N、D和K。
以下N行每行一条日志,包含两个整数ts和id。

对于50%的数据,1 <= K <= N <= 1000
对于100%的数据,1 <= K <= N <= 100000
0 <= ts <= 100000
0 <= id <= 100000

【输出格式】
按从小到大的顺序输出热帖id。每个id一行。

【输入样例】
7 10 2
0 1
0 10
10 10
10 1
9 1
100 3
100 3
【输出样例】
1
3

public class 日志统计 {
	static class R {// 静态外部类,涉及R对象所有的属性
		int ts, id;// ts时刻此id收到一个赞
	}

	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		int N = scanner.nextInt();
		int D = scanner.nextInt();
		int K = scanner.nextInt();
		R[] obj = new R[N];// 存储对象
		for (int i = 0; i < N; i++) {
			R r = new R();
			r.ts = scanner.nextInt();
			r.id = scanner.nextInt();
			obj[i] = r;
		}
		// 按照时间先后排序
		Arrays.sort(obj, new Comparator<R>() {
			@Override
			public int compare(R o1, R o2) {
				return o1.ts - o2.ts;
			}
		});
		Map<Integer, Integer> cnt = new HashMap<Integer, Integer>();
		SortedSet<Integer> set = new TreeSet<>();
		// 尺取法
		int j = 0;
		for (int i = 0; i < obj.length; i++) {
			while (j < N && obj[j].ts - obj[i].ts <= K) {
				int id = obj[j].id;
				Integer isExist = cnt.get(id);
				if (isExist != null) {// 已存在值
					cnt.put(id, cnt.get(id) + 1);// 更新
				} else {
					cnt.put(id, 1);
				}
				if (cnt.get(id) >= K) {
					set.add(id);
				}
				j++;
			}
			// 出来的j不满足条件
			Integer temp = cnt.get(obj[i].id);// 如果当前值不为0,则-1
			if (temp != null) {// 更新i
				cnt.put(obj[i].id, temp - 1);
			}
		}
		for (Integer integer : set)
			System.out.println(integer);
	}
}

九、全球变暖

你有一张某海域NxN像素的照片,".“表示海洋、”#"表示陆地,如下所示:

.......
.##....
.##....
....##.
..####.
...###.
.......

其中"上下左右"四个方向上连在一起的一片陆地组成一座岛屿。例如上图就有2座岛屿。
由于全球变暖导致了海面上升,科学家预测未来几十年,岛屿边缘一个像素的范围会被海水淹没。
具体来说如果一块陆地像素与海洋相邻(上下左右四个相邻像素中有海洋),它就会被淹没。
例如上图中的海域未来会变成如下样子:

.......
.......
.......
.......
....#..
.......
.......

请你计算:依照科学家的预测,照片中有多少岛屿会被完全淹没。
【输入格式】
第一行包含一个整数N。 (1 <= N <= 1000)
以下N行N列代表一张海域照片。
照片保证第1行、第1列、第N行、第N列的像素都是海洋。
【输出格式】
一个整数表示答案。
【输入样例】
7
上图
【输出样例】
1

public class 全球变暖 {
	static int[][] dir = { { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 } };// 下上右左
	static int N;// 数据规模
	static char[][] grid;// 地图数据
	static int ans;// 被完全淹没的岛屿数量
	static boolean[][] flag;// 标记每个格子是否被访问

	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		N = scanner.nextInt();
		scanner.nextLine();
		grid = new char[N][N];
		flag = new boolean[N][N];
		for (int i = 0; i < N; i++) {
			grid[i] = scanner.nextLine().toCharArray();
		}
		// 双重循环检测地图上的每个格子,以未被访问的#开始
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < N; j++) {
				if (grid[i][j] == '#' && !flag[i][j]) {
					bfs(i, j);
				}
			}
		}
		System.out.println(ans);
	}

	public static void bfs(int x, int y) {// 传入位置,搜索周边是否有'#'
		flag[x][y] = true;// 一旦进入bfs,说明被访问
		int cntOfBlock = 0;// 陆地的数量
		int cntOfSubmerge = 0;// 被淹没的陆地数量
		Queue<Point> queue = new LinkedList<>();
		queue.add(new Point(x, y));
		while (!queue.isEmpty()) {
			Point temp = queue.poll();
			cntOfBlock++;// 双重循环决定进入bfs的是陆地
			boolean submerge = false;
			// 查看上下左右是否有'.',有则会被淹没
			for (int i = 0; i < 4; i++) {
				int nextX = temp.x + dir[i][0];
				int nextY = temp.y + dir[i][1];
				if (nextX < 0 || nextX >= N || nextY < 0 || nextY >= N) {
					continue;
				}
				if (grid[nextX][nextY] == '.') {
					submerge = true;
				} else if (grid[nextX][nextY] == '#' && !flag[nextX][nextY]) {
					// 周围有岛屿连成一片,那么应该算一块成群的岛屿,继续往下找
					queue.add(new Point(nextX, nextY));
					flag[nextX][nextY] = true;
				}
			}
			// 有'.'环绕
			if (submerge) {
				cntOfSubmerge++;
			}
		}
		if (cntOfBlock == cntOfSubmerge) {
			ans++;
		}

	}

	static class Point {
		int x, y;

		public Point(int x, int y) {
			super();
			this.x = x;
			this.y = y;
		}

	}
}

十、放弃

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值