由于河北北京这边省赛那两天风很大的原因,所以导致蓝桥杯没有如期进行,所以说来做做已经考完了的人的比赛题目


A. 逃离高塔

A
A

首先如果不考虑溢出处理,用BigInteger估计一是比较慢二是方法写起来比较麻烦,所以还是取模取出个位数处理,因为只看个位数,所以取模不会对结果产生影响

1
2
3
4
5
6
7
8
9
10
11
12
public class A {
public static void main(String[] args) {
int res = 0;
for(int i = 1; i <= 2025; i++) {
// 结果只取个位数,在每次乘方过程中都取模取出个位数,防止溢出
int temp = (int) (((Math.pow(i, 2) % 10) * i) % 10);
if(temp == 3) res++;
}
System.out.print(res);
// 202
}
}

B. 消失的蓝宝

img
img

大模拟题,首先 N 肯定是从 20250413开始遍历,但是直接遍历肯定要慢很多

可以先打表发现,20260411 是第一个满足条件2 的数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 打表代码
public class B {
public static void main(String[] args) {
long res = 0;
for(long n = 20250413; n < Long.MAX_VALUE; n++) {
if((n + 20250412) % 20240413 == 0) {
res = n;
break;
}
if((n + 20240413) % 20250412 == 0) {
res = n;
break;
}
}
System.out.print(res);
// 20260411
}
}

所以从 20260411 开始,每次加 20250412 确保满足条件2,然后判断条件1

1
2
3
4
5
6
7
8
9
10
11
12
public class B {
public static void main(String[] args) {
long res = 0;
for (long N = 20260411; N < Long.MAX_VALUE; N += 20250412) {
if ((N + 20250412) % 20240413 == 0) {
res = N;
break;
}
}
System.out.print(res); // 409876661809331
}
}

C. 电池分组

C
C
img
img

两组电池异或和相等,异或 相同为 0,不同为1,那么相等的数异或和为 0,要求能分成两组, 那么就是意思组内所有数异或后为 0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class C {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int T = sc.nextInt();
for(int i = 1; i <= T; i++) {
int sum = 0;
int N = sc.nextInt(); // 本组电池的数量
for (int j = 1; j <= N; j++) {
int element = sc.nextInt();
sum = sum ^ element;
}
if(sum == 0) {
System.out.println("YES");
}else {
System.out.println("NO");
}
}
}
}

D. 魔法科考试

image-20250419141858122
image-20250419141858122
image-20250419141915330
image-20250419141915330

朴素模拟发现只能过2/ 5的数据,那么该如何优化呢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class Solutions1{
// 质数筛
boolean check(int p) {
for(int i = 2; i * i <= p; i++) {
if(p % i == 0) return false;
}
return true;
}

public Solutions1() {
FastReader sc = new FastReader();
int n = sc.nextInt();
int m = sc.nextInt();
int[] a = new int[n];
int[] b = new int[m];
for (int i = 0; i < n; i++) {
a[i] = sc.nextInt();
}
for (int i = 0; i < m; i++) {
b[i] = sc.nextInt();
}
Arrays.sort(a);
Arrays.sort(b);
int index_a = 0 ,index_b = 0;
for(int i = 0; i < n; i++) {
if(a[i] > n + m) break;
index_a++;
}
for(int i = 0; i < m; i++) {
if(b[i] > n + m) break;
index_b++;
}
//System.out.println(index_a);
//System.out.println(index_b);

HashSet<Integer> set = new HashSet<>(); // 去重
for (int i = 0; i < index_a; i++) {
for (int j = 0; j < index_b; j++) {
if(a[i] + b[j] <= m + n && check(a[i] + b[j])) {
set.add(a[i] + b[j]);
}
}
}

System.out.println(set.size());
}

首先,看到时间限制就知道这题不简单。 我们在这里要注意以下几个点

  1. 素数判定只需要判定这个数的算术平方根及以下的数是不是这个数的因数,这样更节省时间。
  2. 对于已经判定过的数,不需要判断,所以可以打质数筛
  3. if 判断是有顺序的(从左往右判断)所以素数判定放在最后,当前面不符时,会直接退出就不会再素数判定,节省时间。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// AC代码
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Array;
import java.util.StringTokenizer;
import java.util.*;

public class Main {
public static void main(String[] args) {
new Solutions1();
}
}

class Solutions1{
int[] dx = new int[40004];
// 质数筛
boolean check(int p) {
// 已经判定过,就无须再判定
if(dx[p] == 1) return false;
if(dx[p] == 2) return true;
for(int i = 2; i * i <= p; i++) {
if(p % i == 0) {
dx[p] = 1;
return false;
}
}
dx[p] = 2;
return true;
}

public Solutions1() {
FastReader sc = new FastReader();
int n = sc.nextInt();
int m = sc.nextInt();
int[] a = new int[n];
int[] b = new int[m];
for (int i = 0; i < n; i++) {
a[i] = sc.nextInt();
}
for (int i = 0; i < m; i++) {
b[i] = sc.nextInt();
}
Arrays.sort(a);
Arrays.sort(b);
int index_a = 0 ,index_b = 0;
for(int i = 0; i < n; i++) {
if(a[i] > n + m) break;
index_a++;
}
for(int i = 0; i < m; i++) {
if(b[i] > n + m) break;
index_b++;
}
//System.out.println(index_a);
//System.out.println(index_b);

HashSet<Integer> set = new HashSet<>(); // 去重
for (int i = 0; i < index_a; i++) {
for (int j = 0; j < index_b; j++) {
if(a[i] + b[j] <= m + n && check(a[i] + b[j])) {
set.add(a[i] + b[j]);
}
}
}

System.out.println(set.size());
}


class FastReader {
BufferedReader br;
StringTokenizer st;

public FastReader() {
br = new BufferedReader(new InputStreamReader(System.in));
st = new StringTokenizer("");
}

String next() {
while (!st.hasMoreTokens()) {
try {
st = new StringTokenizer(br.readLine());
} catch (IOException e) {
e.printStackTrace();
}
}
return st.nextToken();
}

int nextInt() {
return Integer.parseInt(next());
}
}
}
/*
3 4
2 3 10
3 4 5 1
*/

E. 爆破

img
img
在这里插入图片描述
在这里插入图片描述

题目大意:使用尽可能的小距离使 n 个圆连在一起。

最小生成树,圆和圆的距离可以用圆心距离减半径,用 prim 写吧,算法讲解都在注释里详细写下了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
public class Main {
public static void main(String[] args) {
new Solutions();
}
}

class Solutions {
private int n;

private class cirlce implements Comparable<cirlce> {
int x, y, r;

public cirlce() {
}

public cirlce(int x, int y, int r) {
this.x = x;
this.y = y;
this.r = r;
}

@Override
public int compareTo(cirlce o) {
return this.x - o.x;
}
}

private cirlce[] cirlces = new cirlce[5005];

private double Getdist(int u, int v) {
return Math.sqrt((cirlces[u].x - cirlces[v].x) * (cirlces[u].x - cirlces[v].x) + (cirlces[u].y - cirlces[v].y) * (cirlces[u].y - cirlces[v].y));
}

private double prim() {
boolean[] visited = new boolean[n]; // 用于标记每个顶点是否已经被加入到最小生成树中
double[] dist = new double[n]; // 距离数组
// 初始化
Arrays.fill(visited, false);
Arrays.fill(dist, Double.MAX_VALUE);
// 选择0号为起点,并且设为距离0
dist[0] = 0.0;
double sum = 0.0; // 最小生成树的边总权重
// 每次选择一个顶点加入到最小生成树中
for (int i = 0; i < n; i++) {
int u = -1;
double minDist = Double.MAX_VALUE; // 最小距离指针
// 找到距离最小生成树最近且未被访问的顶点
for (int j = 0; j < n; j++) {
if(!visited[j] && dist[j] < minDist){
minDist = dist[j];
u = j;
}
}
// 没有点可以移出去了,说明全部加入
if(u == -1){
break;
}
visited[u] = true;
sum += minDist;
// 遍历所有未被访问的顶点,更新它们到最小生成树的距离
for(int v = 0; v < n; v++){
if(!visited[v]){
double w;
double spcr = Getdist(u, v);
// 圆心之间距离小于等于半径之和,相交相切
if(spcr <= cirlces[u].r + cirlces[v].r){
w = 0.0; // 不需要搭桥
}else{
w = spcr - (cirlces[u].r + cirlces[v].r);
}
// 松弛操作
if(w < dist[v]){
dist[v] = w;
}
}
}
}
return sum;
}

public Solutions() {
FastReader sc = new FastReader();
n = sc.nextInt();
for (int i = 0; i < n; i++) {
cirlces[i] = new cirlce(sc.nextInt(), sc.nextInt(), sc.nextInt());
}

double sum = prim();
System.out.printf("%.2f", sum);
}

class FastReader {
BufferedReader br;
StringTokenizer st;

public FastReader() {
br = new BufferedReader(new InputStreamReader(System.in));
st = new StringTokenizer("");
}

public String next() {
while (!st.hasMoreTokens()) {
try {
st = new StringTokenizer(br.readLine());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return st.nextToken();
}

public int nextInt() {
return Integer.parseInt(next());
}
}
}

F. 数组翻转

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

贪心,可以发现,答案只跟数组中每个数连续出现的次数有关,如果有两段一样且连续的数但是被分隔开了,那么肯定存在一种翻转方法翻转到答案的数值,所以答案就是统计这两段然后一乘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public class Main {
public static void main(String[] args) {
new Solutions();
}
}

class Solutions {

public Solutions() {
FastReader in = new FastReader();
int n = in.nextInt();
int[] a = new int[n];
for (int a_i = 0; a_i < n; a_i++) {
a[a_i] = in.nextInt();
}
// 桶,记录最大的两个连续值
int[][] backet = new int[1000086][2];

for(int i = 0; i < n; i++){
int cp = 0; // 出现次数
cp++;
// 发现了两个连续的相等的数
while(i < n - 1 && a[i] == a[i + 1]){
cp++;
i++; // 指针跳过相同的数
}
// 第一段出现的更长
if(cp > backet[a[i]][0]){
backet[a[i]][1] = backet[a[i]][0];
backet[a[i]][0] = cp;
// 第二段出现的更长
}else if(cp > backet[a[i]][1]){
backet[a[i]][1] = cp;
}
}

long res = 0;
for(int i = 0; i < 1000003; i++){
// ai * 总段长 (backet[i][1] + backet[i][0])
long temp = (long) i * (backet[i][1] + backet[i][0]);
if(temp > res){
res = temp;
}
}
System.out.println(res);
}


class FastReader {
BufferedReader br;
StringTokenizer st;

public FastReader() {
br = new BufferedReader(new InputStreamReader(System.in));
st = new StringTokenizer("");
}

public String next() {
while (!st.hasMoreTokens()) {
try {
st = new StringTokenizer(br.readLine());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return st.nextToken();
}

public int nextInt() {
return Integer.parseInt(next());
}
}
}

G. 2的幂

在这里插入图片描述
在这里插入图片描述
img
img

数组中所有元素的乘积是 2 的 k 次方倍数。

  • 要做到这一点,数组中所有元素的 2 的幂次之和必须至少为 k。

如果数组中所有元素的 2 的幂次之和大于k,已经符合了,输出0

如果初始的 2 的幂次之和小于 k,则需要通过重新选择或增加数组中的某些元素,来使总的 2 的幂次之和达到 k。

考虑dp

  • 使用一个 DP 数组 dp,其中 dp[j] 表示在增加的 2 的幂次之和为 j 时,所需的最小增加的数的总和。
  • 遍历数组中的每个数 num,计算其在加上某个正整数后,新数能被 2 的 q 次方整除的最小增量cost,并且能够获得的 2 的幂次增量 gain。
  • 更新 DP 数组,即通过考虑当前数的所有可能增加的选项来更新之前的 DP 状态。
  • 如果 dp[re](其中 re 是需要补充的 2 的幂次)仍然是无穷大,说明无法达到目标,输出 -1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package 动态规划.subject.线性dp.P12160_蓝桥杯2025省JavaB_2的幂;

import scala.reflect.internal.util.OwnerOnlyChmod;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;

public class Main {
public static void main(String[] args) {
new Solutions();
}
}

class Solutions{
// 计算一个数是 2 的几次幂
private int countTwos(int num) {
int count = 0;
while(num % 2 == 0 && num > 0){
count++;
num /= 2;
}
return count;
}

public Solutions(){
FastReader fs = new FastReader();
int n = fs.nextInt();
int k = fs.nextInt(); // 目标 2 的幂次
int[] a = new int[n + 1];
for (int i = 1; i <= n; i++) a[i] = fs.nextInt();

// 初始状态 2 的幂次和
int total = 0;
for(int num: a){
total += countTwos(num);
}

if(total >= k){
System.out.println(0);
return;
}

// 需要新增的2的幂次
int rp = k - total; // 差2的rp次幂
int[] dp = new int[rp + 1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0; // 初始化dp[0]为0,意思是1为初态,不加

// 处理每个数
// 数组中所有元素的乘积是 2 的 k 次方倍数。
// 要做到这一点,数组中所有元素的 2 的幂次之和必须至少为 k。
for (int i = 0; i < n; i++) {
int num = a[i];
int p = countTwos(num); // 该数为2的几次幂

// 临时 DP 数组,避免同一组重复选择
int[] tempDp = dp.clone();

// 生成增量选项,开到30差不多
for (int q = p + 1; q <= 30; q++) {
long mp = (long)Math.pow(2, q); // 2^q
if(mp > 100000) break; // 注意最大值限制
// 不小于 num 且是 2^q 倍数的最小数 sp
long sp = ((long)num + mp - 1) / mp * mp;
// 新数能被 2 的 q 次方整除的最小增量cost 和 能够获得的 2 的幂次增量 gain。
int cost = (int)(sp - num);
int gain = countTwos((int)sp) - p;
if (gain <= 0) continue;

// 更新dp
for(int j = rp; j >= gain; j--){
// dp[j - gain] 不是无穷大,存在一种方式可以到达 j - gain 的 2 的幂次增量
if(dp[j - gain] != Integer.MAX_VALUE){
tempDp[j] = Math.min(tempDp[j], dp[j - gain] + cost);
}
}
}
}
System.out.println(dp[rp] == Integer.MAX_VALUE? -1 : dp[rp]);
}

class FastReader {
BufferedReader br;
StringTokenizer st;

public FastReader() {
br = new BufferedReader(new InputStreamReader(System.in));
st = new StringTokenizer("");
}

public String next() {
while (!st.hasMoreTokens()) {
try {
st = new StringTokenizer(br.readLine());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return st.nextToken();
}

public int nextInt() {
return Integer.parseInt(next());
}
}
}

H. 研发资源分配

img
img
img
img

可以发现,题目思路类似于田忌赛马,贪心考虑,用下等马换掉对方的上等马

例如题解 1 3 2 用 2 1 3,就是用 最下等的 1 抵掉了 最上等的 3

而且由于是全排列,所以剩下的所有位置一定可以排出都比对方大的排列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 我是真不知道为什么洛谷一直给我一个 WA 我也不知道哪里错了)

public class Main {
public static void main(String[] args) {
new Solutions2();
}
}
// 1 2 3 2 3 1 1+2-3=0
// 1 4 3 2 2 1 4 3 1-2+3+4=6
// 1 3 2 4 2 4 3 1 1+2+3-4=2
// 2 3 1 5 4 3 4 2 1 5 1+2+3-4+5
// 经过了几个案例的打表,我们发现,总是存在这样一种排列,会使得资源份额的差值最大
// 那么就是 对面最多只能拿到一天的分数,而且拿到的只能是等级最高的那天

class Solutions2{
public Solutions2(){
FastReader sc = new FastReader();
int n = sc.nextInt();
int[] b = new int[n + 1];
for (int i = 1; i <= n; i++) b[i] = sc.nextInt();

if (n == 1) {
System.out.println(0);
return;
}

long res = 0;
for (int i = 1; i <= n; i++) {
// 对面出动了最大的等级,我们使用最下等的去换取,这天的不要
if(b[i] == n){
res -= i;
}else{
// 别的情况我们都能凑出一种排列比他大,因为我们用最小的把他最大的给抵出去了
res += i;
}
}
System.out.println(res);
}

class FastReader {
BufferedReader br;
StringTokenizer st;

public FastReader() {
br = new BufferedReader(new InputStreamReader(System.in));
st = new StringTokenizer("");
}

public String next() {
while (!st.hasMoreTokens()) {
try {
st = new StringTokenizer(br.readLine());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return st.nextToken();
}

public int nextInt() {
return Integer.parseInt(next());
}
}
}
image-20250419164239152
image-20250419164239152