蓝桥杯 1024 第 2 场算法双周赛 解题报告

前言


整体评价

挺有难度的一场比赛,听说特意加了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段了

可以观察地发现

\sum_{i=1}^{i=k} G_i \Rightarrow \sum_{i=1}^{i=k} (a_{i1} - a_{i0})

a_{i,1}, a_{i+1,0}两元素相邻

所以可以在转换为

\sum_{i=1}^{i=k} G_i \Rightarrow \sum_{i=1}^{i=k} (a_{i1} - a_{i0}) \Rightarrow a_{n-1} - a_0 + \sum_{i=1}^{i=k-1} (a_{i,1} - a_{i+1,0})

所以要使得作差和最小,就是求k-1个最小的相邻项

f(i) = a_i - a_{i+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. 串门

树的直径经典题

可以尝试模拟跑遍所有的树节点,可以发现,遍历路径存在两种模式

  • 单行道
  • 回头路

这个一般解为

\sum 2 * w(e) - \sum_{u\in link} w(u)

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;
        }
    }

}


写在最后

  • 7
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值