第9届蓝桥杯Java组省赛

A. 第几天

题目描述:

题目描述

2000年的1月1日,是那一年的第1天。
那么,2000年的5月4日,是那一年的第几天?

输出格式

输出一个整数表示答案

思路解析:

2000年为闰年(366天),判断闰年平年的方式为:能被400整除或者能被4整除不能被100整除。

挨个模拟每一天

代码:

package9;

/**
 * @author: DreamCode
 * @file: A_第几天.java
 * @time: 2022年3月21日-下午9:54:37
 * @答案:125
 */
public class A_第几天 {

	static int[] arr= {31,29,31,30,31,30,31,31,30,31,30,31};
	public static void main(String[] args) {
		int ans = 0;
		for(int i=0;i<4;i++) {  //前4个月的天数
			ans+=arr[i];
		}
		ans+=4;
		System.out.println(ans);
	}

}

B. 方格计数

题目描述:

题目描述

如图所示,在二维平面上有无数个1x1的小方格。
我们以某个小方格的一个顶点为圆心画一个半径为 1000 的圆。
你能计算出这个圆里有多少个完整的小方格吗?
img

输出格式

输出一个整数表示答案

思路解析:

考虑到圆的对称性,我们只需要计算第一象限的方格数ans,最终的答案就为ans×4;计算第一象限的方格数时,挨个模拟每个方格数的右上角坐标,根据距离公式判断右上角坐标与圆心的距离是否小于等于R,满足条件则整个方格在圆中。

代码:

package9;

/**
 * @author: DreamCode
 * @file: B_方格计数.java
 * @time: 2022年3月21日-下午9:58:38
 * @答案: 3137548
 */
public class B_方格计数 {

	public static void main(String[] args) {
		int res=0;
		int r=1000;
		for(int x=1;x<=1000;x++) {
			for(int y=1;y<=1000;y++) {
				if(distance(x,y)<=r) {
					res++;
				}
			}
		}
		System.out.println(res*4);

	}

	private static double distance(int x, int y) {
		double d=Math.sqrt(x*x+y*y);
		return d;
	}

}

C. 复数幂

题目描述:

题目描述

设i为虚数单位。对于任意正整数n,(2+3i)^n 的实部和虚部都是整数。
求 (2+3i)^123456 等于多少? 即(2+3i)的123456次幂,这个数字很大,要求精确表示。
答案写成 “实部±虚部i” 的形式,实部和虚部都是整数(不能用科学计数法表示),中间任何地方都不加空格,实部为正时前面不加正号。
(2+3i)^2 写成: -5+12i,(2+3i)^5 的写成: 122-597i

输出格式

按格式输出答案

思路解析:

大整数类的使用,因为结果字符串特别长,所以输出应该指定为文件

代码:

package9;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.math.BigInteger;

/**
 * @author: DreamCode
 * @file: B_方格计数.java
 * @time: 2022年3月21日-下午9:58:38
 */
public class C_复数幂 {

	public static void main(String[] args) throws IOException {
		BigInteger a = new BigInteger("2");
		BigInteger b = new BigInteger("3");
		for (int i = 0; i < 123455; i++) {
			BigInteger c = a;
			BigInteger d = b;
			a = (new BigInteger("2").multiply(c)).subtract(new BigInteger("3").multiply(d));
			b = (new BigInteger("2").multiply(d)).add(new BigInteger("3").multiply(c));
		}
		System.setOut(new PrintStream(new File("src/第9届/C_OutPut.txt")));
		System.out.print(a);
		if (b.compareTo(BigInteger.ZERO) > 0) {
			System.out.print('+');
		}
		System.out.print(b);
		System.out.print("i");
	}
}

D. 测试次数

题目描述:

题目描述

x星球的居民脾气不太好,但好在他们生气的时候唯一的异常举动是:摔手机。
各大厂商也就纷纷推出各种耐摔型手机。x星球的质监局规定了手机必须经过耐摔测试,并且评定出一个耐摔指数来,之后才允许上市流通。
x星球有很多高耸入云的高塔,刚好可以用来做耐摔测试。
塔的每一层高度都是一样的,与地球上稍有不同的是,他们的第一层不是地面,而是相当于我们的2楼。
如果手机从第7层扔下去没摔坏,但第8层摔坏了,则手机耐摔指数=7。
特别地,如果手机从第1层扔下去就坏了,则耐摔指数=0。
如果到了塔的最高层第n层扔没摔坏,则耐摔指数=n
为了减少测试次数,从每个厂家抽样3部手机参加测试。
某次测试的塔高为1000层,如果我们总是采用最佳策略,在最坏的运气下最多需要测试多少次才能确定手机的耐摔指数呢?

输出格式

输出一个整数表示答案

思路解析:

动态规划法。难点在于如何理解最佳策略与最坏的运气。我们考虑在测试n层的耐摔指数,有k部手机。在每一层模拟摔手机,只有两种结果,第一种为测试当前层p层时摔坏了,则还需要用k-1部手机去测剩下的p-1【0,1,2…p-1】层;第二种为当前层未摔坏,则我们还需要用这k个手机测试剩下的n-p【p+1,p+2…n】层,这里的最坏运气就是指获取两种结果中测试的次数最多的一种结果。最佳策略问题,因为对于测试n层,我们的开始测试的层数选择p可以从1到n,最佳策略,即测试次数最少,所以最佳策略即从1到n开始耐摔获取其中测试次数最少的策略。

因此,状态转移方程:

f(n,k) = min {for i in n: max [ f(i-1,k-1), f(n-i,k) ] }

代码:

package9;

/**
 * @author: DreamCode
 * @file: D_测试次数.java
 * @time: 2022年3月21日-下午11:04:09 @答案:19
 */
public class D_测试次数 {

	public static void main(String[] args) {
		int n = 1000;
		int k = 3;
		int ans = DP(k, n);
		System.out.println(ans);
	}

	private static int DP(int k, int n) {
		int[][] dp = new int[k + 1][n + 1];
		for (int i = 1; i <= n; i++) {
			dp[1][i] = i; // 只有一部手机的时候,测试n层肯定是n次
		}
		for (int i = 2; i <= k; i++) { // 手机数从2-k
			for (int j = 1; j <= n; j++) { // 测试层数从1-n
				int strategy = Integer.MAX_VALUE; // 记录最佳策略结果
				for (int p = 1; p <= j; p++) { // 测试j层有i部手机,模拟分别从1-j层开始摔,找最坏运气,最佳策略结果
					int N = dp[i][j - p];// 当前层没摔坏,则需要用i部手机测试剩下的j-p层
					int Y = dp[i - 1][p - 1];// 当前层摔坏了,则需要用i-1部手机测试剩下的p-1层
					int bad_luck = 1 + Math.max(N, Y); // 最坏的运气
					strategy = Math.min(strategy, bad_luck);// 最佳丢手机策略
				}
				dp[i][j] = strategy;
			}
		}
		return dp[k][n];
	}

}

E. 快速排序

代码填空题

package9;

import java.util.Random;

/**
 * @author: DreamCode
 * @file: E_快速排序.java
 * @time: 2022年3月21日-下午11:05:26
 */
public class E_快速排序 {

	/**
	 * 以下代码可以从数组a[]中找出第k小的元素。
	 * 
	 * 它使用了类似快速排序中的分治算法,期望时间复杂度是O(N)的。
	 * 
	 * 请仔细阅读分析源码,填写划线部分缺失的内容。
	 */
	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(a,i+1,r,k-i+1); // 填空
		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));
	}
}

F. 递增三元组

题目描述:

题目描述

给定三个整数数组
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。
1 <= N <= 100000 0 <= Ai, Bi, Ci <= 100000

输出格式

一个整数表示答案

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

思路解析:

分别将A、B、C三个数组按照从小到达排序,遍历B数组,每次在A数组中找到最后一个比B[i]小的数的位置p,在C数组中找到第一个比B[i]大的数位置q,当前B[i]可构成的三元组数目为§*(N-q),对每个B[i]累计做和,注意,用 long 存储

package9;

import java.util.Arrays;
import java.util.Scanner;

public class F_递增三元组{

	public static void main(String[] args) {
		Scanner scanner=new Scanner(System.in);
		int N=scanner.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]=scanner.nextInt();			
		}
		for(int i=0;i<N;i++) {
			B[i]=scanner.nextInt();	
		}
		for(int i=0;i<N;i++) {
			C[i]=scanner.nextInt();
		}
		Arrays.sort(A);
		Arrays.sort(B);
		Arrays.sort(C);
		long sum=0;
		int p=0;
		int q=0;
		for(int i=0;i<N;i++) {  
			while(p<N&&A[p]<B[i]) {
				p++;
			}
			while(q<N&&C[q]<=B[i]) {
				q++;
			}
			sum+=(long)(p)*(N-q);
		}
		System.out.println(sum);
	}
}

G. 螺旋折现

试题描述:

题目描述

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

输入格式

X和Y,数据在int范围以内。

输出格式

输出dis(X, Y)

输入样例
0 1
输出样例
3

思路解析:

对角线的规律问题,分四种象限考虑。画出y=x与y=-x两条线,可以逐步构造出边长为2,4,6…2*k的正方形,k为坐标点的max(abs(x),abs(y)),然后分别讨论四种情况。注意结果需要用long存储

代码:

package9;

import java.util.Scanner;

/**
 * @author: DreamCode
 * @file: G_螺旋折线.java
 * @time: 2022年3月23日-下午8:16:00
 */
public class G_螺旋折线 {

	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		long x = scanner.nextLong();
		long y = scanner.nextLong();
		long k = Math.max(Math.abs(x), Math.abs(y));
		long sum = (8 + 8 * k) * k / 2;
		long d = 0;// 计算与当前边长正方形左下角坐标的距离,并将sum减去距离值
		if (y == k) { // 上边界
			d = k - x + 4 * k;
		} else if (x == k) {// 右边界
			d = y - (-k) + 2 * k;
		} else if (y == -k) {// 下边界
			d = x - (-k);
		} else if (x == -k) {// 左边界
			d = k - y + 6 * k;
		}
		sum -= d;
		System.out.println(sum);
	}
}

H. 日志统计

题目描述:

题目描述

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

输入格式

第一行包含三个整数N、D和K。
以下N行每行一条日志,包含两个整数ts和id。
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

思路解析:

对所有的日志记录按照时间进行排序,按照尺取法遍历所有日志,找出符合要求的id

代码:

package9;

import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeSet;

/**
 * @author: DreamCode
 * @file: H_日志统计.java
 * @time: 2022年3月23日-下午8:56:03
 */
public class H_日志统计 {

	static class log {
		public int ts;
		public int td;

		public log(int ts, int td) {
			super();
			this.ts = ts;
			this.td = td;
		}
	}

	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		int N = scanner.nextInt();
		int D = scanner.nextInt();
		int K = scanner.nextInt();
		log[] arr = new log[N];
		for (int i = 0; i < N; i++) {
			arr[i] = new log(scanner.nextInt(), scanner.nextInt());
		}
		Arrays.sort(arr, new Comparator<log>() {  //指定排序规则
			@Override
			public int compare(log o1, log o2) {
				if (o1.ts == o2.ts) {
					return o1.td < o2.td ? -1 : 1;
				}
				return o1.ts < o2.ts ? -1 : 1;
			}
		});
		solve(arr, D, K);
	}

	private static void solve(log[] arr, int D, int K) {
		// TODO 尺取法解决问题
		Map<Integer, Integer> rec = new HashMap<>(); // hash表记录每种id的点赞数
		Set<Integer> ans = new TreeSet<>(); // 记录热帖的td,默认从小到大排序且去重
		int p = 0, q = 0; // 尺取法的双指针
		while (q < arr.length) { // 遍历每一种情况
			while (arr[q].ts < arr[p].ts + D) {
				q++;
				if(q==arr.length) {
					break;
				}
			}
			for (int i = p; i < q; i++) { // 一次尺,尺子的长度满足[t,t+D);
				if (rec.get(arr[i].td) == null) {
					rec.put(arr[i].td, 1);
				} else {
					rec.put(arr[i].td, rec.get(arr[i].td) + 1);
				}
			}
			for (Map.Entry<Integer, Integer> entry : rec.entrySet()) { // 查询当前的尺满足热帖要求的id
				if (entry.getValue() >= K) {
					ans.add(entry.getKey());
				}
			}
			rec.clear(); //每一次尺取完以后将记录数组清空
			p++;
		}
		Iterator<Integer> iterator=ans.iterator();
		while(iterator.hasNext()) {
			System.out.println(iterator.next());
		}
	}
}

I. 全球变暖

题目描述:

题目描述

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

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

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

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

请你计算:依照科学家的预测,照片中有多少岛屿会被完全淹没。

输入格式

第一行包含一个整数N。 (1 <= N <= 1000)
以下N行N列代表一张海域照片。
照片保证第1行、第1列、第N行、第N列的像素都是海洋。

输出格式

一个整数表示答案。

输入样例
7 
.......
.##....
.##....
....##.
..####.
...###.
.......  
输出样例
1

思路解析:

BFS+连通块判断。因为涉及到N<=1000,选择用DFS遍历可能会栈溢出。遍历整个地图,没遇到一个 ‘#’ 并且未访问,则按照此点向四周BFS,用两个计数器cnt1与cnt2分别记录这个岛屿中陆地靠近海洋的数量与不靠近海洋的数量,如果最终cnt1与cnt2相等,则断定该岛屿连通,最终会被淹没。

代码:

package9;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

/**
 * @author: DreamCode
 * @file: I_全球变暖.java
 * @time: 2022年3月24日-下午10:58:21
 */
public class I_全球变暖 {

	static class point {
		public int x;
		public int y;

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

	}

	static int[] x_stride = { -1, 1, 0, 0 }; // 分别代表上下左右移动
	static int[] y_stride = { 0, 0, -1, 1 };
	static boolean[][] vis; // 记录地图上的该点是否已访问
	static char[][] map; // 记录地图上的元素
	static int ans = 0; // 记录最终会被淹没的岛屿数量
	static int n;

	public static void main(String[] args) {
		Scanner scanner = new Scanner(new BufferedInputStream(System.in));
		n = scanner.nextInt();
		map = new char[n][n];
		vis = new boolean[n][n];
		for (int i = 0; i < n; i++) {
			map[i] = scanner.next().toCharArray();
		}
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < n; j++) {
				if (map[i][j] == '#' && !vis[i][j]) { // 当前点未访问且当前点未 # ,以该点为中心进行BFS判断是否连通
					BFS(i, j);
				}
			}
		}
		System.out.println(ans);
	}

	private static void BFS(int x, int y) {
		// TODO BFS遍历
		int cnt1 = 0; // 记录靠近海洋的陆地数量
		int cnt2 = 0; // 记录陆地的数量
		Queue<point> queue = new LinkedList<>(); // 构造队列存储每一个点
		queue.offer(new point(x, y));
		while (!queue.isEmpty()) {
			cnt2++;
			point p = queue.peek();
			queue.remove();
			vis[p.x][p.y] = true; // 标记该点已访问
			boolean flag = false; // 默认当前陆地点不靠近海洋
			for (int i = 0; i < 4; i++) {// 遍历四个方向
				int next_x = p.x + x_stride[i];
				int next_y = p.y + y_stride[i];
				if (next_x >= 0 && next_x < n && next_y >= 0 && next_y < n) { // 边界判断
					if (map[next_x][next_y] == '.') { // 当前的陆地点附近具有海洋,flag设为true
						flag = true;
					}
					if (map[next_x][next_y] == '#' && !vis[next_x][next_y]) {// 附近点具有未访问的陆地,加入队列
						queue.offer(new point(next_x, next_y));
					}
				}
			}
			if (flag) { // 当前的陆地点附近具有海洋
				cnt1++;
			}
		}
		if (cnt1 == cnt2) { // 当前岛屿的每一块陆地都靠海,会被淹没
			ans++;
		}
	}
}

J. 堆的计数

题目描述:

题目描述

我们知道包含N个元素的堆可以看成是一棵包含N个节点的完全二叉树。
每个节点有一个权值。对于小根堆来说,父节点的权值一定小于其子节点的权值。
假设N个节点的权值分别是1~N,你能求出一共有多少种不同的小根堆吗?
例如对于N=4有如下3种:
1
/
2 3
/
4
1
/
3 2
/
4
1
/
2 4
/
3
由于数量可能超过整型范围,你只需要输出结果除以1000000009的余数。

输入格式

一个整数N

输出格式

一个整数表示答案

输入样例
4
输出样例
3

思路解析:

涉及动态规划、快速幂运算、模的逆元、完全二叉树左右孩子的个数;

费马小定理:如果p是一个质数,而整数a不是p的倍数,则有a^(p-1)≡1(mod p),即a的逆元为 a!^(p-2)

代码:

package9;

import java.util.Scanner;

/**
 * @author: DreamCode
 * @file: J_堆的计数.java
 * @time: 2022-4-7-22:41:26
 */

public class J_堆的计数 {
	private static final int MOD = 1000000009;  //质数
	private static int N;
	private static int[] size; // 记录每个节点的size
	private static long[] jie; // 记录1~N的阶乘
	private static long[] ni; // 记录1~N的阶乘的逆元

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		N = sc.nextInt();
		size = new int[N + 1];
		jie = new long[N + 1];
		ni = new long[N + 1];
		initSize();  //预处理每一个节点的左右子树节点的个数
		initJie();  //预处理阶层与数的逆元
		System.out.println(dp());
		sc.close();
	}

	private static long dp() {  //动态规划递推
		long[] d = new long[N + 1]; // d[i]表示的是i号节点作为根,小根堆的种数
		for (int x = N; x >= 1; x--) {
			if (2 * x + 1 <= N) {
				d[x] = c(size[x] - 1, size[2 * x]) * d[2 * x] % MOD * d[2 * x + 1] % MOD;
			} else if (2 * x <= N) {
				d[x] = c(size[x] - 1, size[2 * x]) * d[2 * x] % MOD;
			} else {
				d[x] = 1;
			}
		}
		return d[1];
	}

	private static void initJie() {  //预处理阶乘与模的逆元
		jie[0] = 1;
		ni[0] = 1;
		for (int i = 1; i <= N; i++) {
			jie[i] = jie[i - 1] * i % MOD;  //递推计算阶乘
			ni[i] = qpow(jie[i], MOD - 2);  //i的逆元为i!^(MOD-2)
		}
	}

	private static long qpow(long a, int n) {  //快速幂运算
		if (a == 0) {
			return 0;
		}
		long ans = 1;
		long x = a;
		while (n > 0) {
			if ((n & 1) == 1) {
				ans = ans * x % MOD;
			}
			n >>= 1;
			x = x * x % MOD;
		}
		return ans;
	}

	private static void initSize() {  //初始化每个节点左右子节点的数目。左节点的数目+右节点的数目+1
		for (int i = N; i >= 1; i--) {
			size[i] = (2 * i <= N ? size[2 * i] : 0) + (2 * i + 1 <= N ? size[2 * i + 1] : 0) + 1;
		}
	}

	private static long c(int n, int r) {  //求组合数:n!/r!/(n-r)!
		return jie[n] * ni[r] % MOD * ni[n - r] % MOD;
	}
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦码城

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值