前言
整体评价
挺有难度的一场比赛,听说特意加了A题,就是为防止暴零。
A. 新生
Q: 2024年是第几界蓝桥杯
其实题目已经说了 小蓝 拿了 第14界的冠军
所以答案为 15
public class Main {
public static void main(String[] args) {
System.out.println(15);
}
}
B. 铺地板
用2x3的地砖,铺设NxM的地板
这题作者疯狂地暗示,大胆的猜
所以这题属于猜结论
长宽皆大于1,且面积为6的倍数
如果非要证明的话,可以从数据归纳法出发,来推演
import java.io.BufferedInputStream;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(new BufferedInputStream(System.in));
int t = sc.nextInt();
while (t -- > 0) {
int h = sc.nextInt(), w = sc.nextInt();
if (h <= 1 || w <= 1 || ((long)h * w % 6 != 0)) {
System.out.println("No");
} else {
System.out.println("Yes");
}
}
}
}
C. 摆玩具
一个长度为n的排序数组,分割为k段,使得每段作差和最小
初看此题,以为是划分型DP,但是n,k值域都很大
所以这题可能要往贪心上去思索,从已排序这个关键字切入
假设这个数组已经切分为k段了
可以观察地发现
且两元素相邻
所以可以在转换为
所以要使得作差和最小,就是求k-1个最小的相邻项
import java.io.BufferedInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(new BufferedInputStream(System.in));
int n = sc.nextInt(), k = sc.nextInt();
int[] arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = sc.nextInt();
}
// 构建f(i) = a(i) - a(i+1)
List<Integer> f = new ArrayList<>();
for (int i = 0; i < n - 1; i++) {
f.add(arr[i] - arr[i + 1]);
}
Collections.sort(f);
long res = arr[n - 1] - arr[0];
for (int i = 0; i < k - 1; i++) {
res += f.get(i);
}
System.out.println(res);
}
}
D. 通关
这题是道拓扑好题
除了原本拓扑排序的依赖之外,还额外添加了阈值和奖赏
这题求的最多的关卡,其最优解其实和决策取舍并无关系。
在多个可选的关卡中,优先选择阈值低的,一定是最优解。
所以在原先的拓扑排序框架中,使用优先队列代替之前的队列即可。
这种贪心思路,一定是最优解法。
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(new BufferedInputStream(System.in));
int n = sc.nextInt();
int p = sc.nextInt();
List<Integer>[]g = new List[n + 1];
Arrays.setAll(g, x->new ArrayList<>());
int[] deg = new int[n + 1];
int[][] ps = new int[n + 1][3];
for (int i = 1; i <= n; i++) {
ps[i][0] = sc.nextInt();
ps[i][1] = sc.nextInt();
ps[i][2] = sc.nextInt();
deg[i]++;
if (ps[i][0] != 0) {
g[ps[i][0]].add(i);
}
}
// 拓扑排序, 用优先队列,代替FIFO队列
PriorityQueue<int[]> pp = new PriorityQueue<>(Comparator.comparing(x -> x[1]));
pp.offer(new int[] {1, ps[1][2]});
int res = 0;
long px = p;
while (!pp.isEmpty()) {
int[] cur = pp.poll();
int u = cur[0];
if (px < ps[u][2]) break;
px += ps[u][1];
res++;
for (int v: g[u]) {
if (--deg[v] == 0) {
pp.offer(new int[] {v, ps[v][2]});
}
}
}
System.out.println(res);
}
}
E. 串门
树的直径经典题
可以尝试模拟跑遍所有的树节点,可以发现,遍历路径存在两种模式
- 单行道
- 回头路
这个一般解为
w(e) 为边权, link为树上的一条特殊链
因为可以自由选择从某个节点出发,因此换根DP,是非常显然的一种解法
不过这里有一种更优秀的做法,寻找树的最大权直径
求树的直径,其实有多种思路
- 换根
- 树形DP
- 图的直径
这三种解法都很有趣
import java.io.*;
import java.util.*;
public class E2 {
static class Solution {
static long inf = Long.MAX_VALUE / 10;
public long solve(int n, List<int[]> []g) {
// 引入求图的直径
long acc = 0;
for (List<int[]> cg: g) {
for (int[] e: cg) {
acc += e[1];
}
}
long[] res1 = bfs(0, n, g);
long[] res2 = bfs((int)res1[0], n, g);
return acc - res2[1];
}
static long[] bfs(int u, int n, List<int[]> []g) {
long[] res = new long[n];
Arrays.fill(res, inf);
Deque<Integer> deq = new ArrayDeque<>();
deq.offer(u);
res[u] = 0;
while (!deq.isEmpty()) {
int cur = deq.poll();
for (int[] e: g[cur]) {
int v = e[0];
if (res[v] == inf) {
res[v] = res[cur] + e[1];
deq.offer(v);
}
}
}
int idx = 0;
for (int i = 0; i < n; i++) {
if (res[i] > res[idx]) {
idx = i;
}
}
return new long[] {idx, res[idx]};
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(new BufferedInputStream(System.in));
int n = sc.nextInt();
List<int[]>[]g = new List[n];
Arrays.setAll(g, x-> new ArrayList<>());
for (int i = 0; i < n - 1; i++) {
int u = sc.nextInt() - 1;
int v = sc.nextInt() - 1;
int w = sc.nextInt();
g[u].add(new int[] {v, w});
g[v].add(new int[] {u, w});
}
Solution solution = new Solution();
long r = solution.solve(n, g);
System.out.println(r);
}
}
F. 神奇数
一眼数位DP,而且条件还蛮少的
这题唯一难的就是,前缀和是最后一个数字的倍数
这个状态挺难维护的,所以可以枚举最后一位数字。
然后在套用数位DP的模板
import java.io.BufferedInputStream;
import java.util.Scanner;
public class Main {
static long MOD = 998244353l;
static class Solution {
Long[][] opt;
long solve(char[] lstr, char[] rstr, int xxx) {
opt = new Long[10][lstr.length];
long r1 = dfs(lstr, 0, 0, true, false, xxx);
opt = new Long[10][rstr.length];
long r2 = dfs(rstr, 0, 0, true, false, xxx);
long res = r2 - r1 + (valid(lstr, xxx) ? 1 : 0);
return ((res % MOD) + MOD) % MOD;
}
long dfs(char[] str, int s, int r, boolean limited, boolean isNum, int xxx) {
if (s >= str.length - 1) {
if (isNum && r == 0) {
if (!limited) return 1;
if (limited && str[s] - '0' >= xxx) return 1;
}
return 0;
}
long res = 0;
if (!isNum) {
res += dfs(str, s + 1, r, false, false, xxx);
}
if (isNum && !limited) {
if (opt[r][s] != null) {
return opt[r][s];
}
}
int start = isNum ? 0 : 1;
int end = limited ? (str[s] - '0') : 9;
for (int i = start; i <= end; i++) {
res += dfs(str, s + 1, (r + i) % xxx, limited && i == (str[s] - '0'), true, xxx);
res %= MOD;
}
if (!limited && isNum) {
opt[r][s] = res;
}
return res;
}
boolean valid(char[] str, int xxx) {
if (str.length == 1) return false;
if (str[str.length - 1] - '0' != xxx) return false;
int acc = 0;
for (int i = 0; i < str.length - 1; i++) {
acc += (str[i] - '0');
acc %= xxx;
}
if (acc == 0) return true;
return false;
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(new BufferedInputStream(System.in));
String l = sc.next();
String r = sc.next();
char[] lstr = l.toCharArray();
char[] rstr = r.toCharArray();
long ans = 0;
for (int i = 1; i <= 9; i++) {
Solution solution = new Solution();
long res = solution.solve(lstr, rstr, i);
ans = (ans + res) % MOD;
}
System.out.println(ans);
}
}
G. 小蓝的疑问
也是很典的一类题
- Dsu On Tree,启发式合并
- DFN序 + 线段树做RMQ
import java.io.BufferedInputStream;
import java.util.*;
import java.util.stream.Collectors;
public class Main {
static class Solution {
int n;
List<Integer> []g;
int[] ww;
int[] solve(int n, int[] ww, List<Integer> []g, int[][] qs) {
this.n = n;
this.ww = ww;
this.g = g;
this.start = new int[n + 1];
this.end = new int[n + 1];
this.depth = new int[n + 1];
dfn(1, -1, 0);
// 构建一个操作队列
List<int[]> ops = new ArrayList<>();
for (int i = 1; i <= n; i++) {
ops.add(new int[] {0, i, depth[i]});
}
for (int i = 0; i < qs.length; i++) {
ops.add(new int [] {1, i, depth[qs[i][0]] + qs[i][1]});
}
Collections.sort(ops, (a, b) -> {
int r = Integer.compare(a[2], b[2]);
if (r != 0) return r;
if (a[0] != b[0]) return a[0] < b[0] ? -1 : 1;
return 0;
});
int[] res = new int[qs.length];
RmqSegTree tree = new RmqSegTree(0, timestamp);
int j = 0;
for (int i = 0; i < ops.size(); i++) {
int[] e = ops.get(i);
int t = e[0];
if (t == 0) {
tree.update(start[e[1]], ww[e[1]]);
} else {
int xxx = e[2];
while (j <= i && ops.get(j)[2] < xxx) {
int[] y = ops.get(j);
if (y[0] == 0) {
tree.update(start[y[1]], 0);
}
j++;
}
int idx = qs[e[1]][0];
int l = start[idx], r = end[idx];
res[e[1]] = tree.query(l, r);
}
}
return res;
}
int timestamp = 0;
int[] start;
int[] end;
int[] depth;
void dfn(int u, int fa, int x) {
depth[u] = x;
start[u] = end[u] = ++timestamp;
for (int v: g[u]) {
if (v == fa) continue;
dfn(v, u, x + 1);
}
end[u] = timestamp;
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(new BufferedInputStream(System.in));
int n = sc.nextInt();
int q = sc.nextInt();
List<Integer>[] g = new List[n + 1];
Arrays.setAll(g, x -> new ArrayList<>());
int[] ww = new int[n + 1];
for (int i = 1; i <= n; i++) {
ww[i] = sc.nextInt();
}
for (int i = 0; i < n - 1; i++) {
int u = sc.nextInt(), v = sc.nextInt();
g[u].add(v);
g[v].add(u);
}
int[][] qs = new int[q][2];
for (int i = 0; i < q; i++) {
qs[i][0] = sc.nextInt();
qs[i][1] = sc.nextInt();
}
Solution solution = new Solution();
int[] res = solution.solve(n, ww, g, qs);
System.out.println(Arrays.stream(res).mapToObj(String::valueOf).collect(Collectors.joining("\n")));
}
static
class RmqSegTree {
int l, r;
RmqSegTree left, right;
int val;
public RmqSegTree(int l, int r) {
this.l = l;
this.r = r;
if (l == r) {
this.val = Integer.MIN_VALUE;
} else {
int m = l + (r - l) / 2;
this.left = new RmqSegTree(l, m);
this.right = new RmqSegTree(m + 1, r);
this.val = Integer.MIN_VALUE;
}
}
public void update(int p, int val) {
if (p < l || p > r) return;
if (l == r) {
this.val = val;
} else {
int m = l + (r - l) / 2;
if (p <= m) {
this.left.update(p, val);
} else {
this.right.update(p, val);
}
this.val = Math.max(this.left.val, this.right.val);
}
}
public int query(int l, int r) {
if (l <= this.l && r >= this.r) {
return this.val;
}
int m = this.l + (this.r - this.l) / 2;
if (l <= m && r > m) {
int val1 = this.left.query(l, m);
int val2 = this.right.query(m + 1, r);
return Math.max(val1, val2);
} else if (r <= m) {
return this.left.query(l, r);
} else if (l > m) {
return this.right.query(Math.max(l, m + 1), r);
}
return this.val;
}
}
}