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

前言


整体评价

这是蓝桥云课的第一场公开周赛,还是挺用心的。因为第一场比赛,整体比赛难度还是有所放低。


A. 三带一

Q: 四张手牌,是否能构成3带1的牌型

签到题, 有多种思路

  • 最小表示式

        排序后,一定呈现 AAAB, 或者 ABBB 型的牌,注意 A != B

  • 计数统计

        一定存在计数为3和计数为1的key,注意只有4张牌

import java.io.*;
import java.util.*;

public class Main {

    public static void main(String[] args) {
        Scanner sc = new Scanner(new BufferedInputStream(System.in));
        int t = sc.nextInt();
        while (t-- > 0) {
            char[] str = sc.next().toCharArray();
            Map<Integer, Integer> hash = new HashMap<>();
            for (char c: str) {
                hash.merge((int)c, 1, Integer::sum);
            }
            // 同时包含计数1和计数3,只有4张牌,则一定是3带1
            boolean res = hash.values().contains(1) && hash.values().contains(3);
            System.out.println(res ? "Yes" : "No");
        }
    }

}

B. 数树数

Q: 满二叉树,根据指令左右向下,求最后的节点代表的数值

找规律的题,其实可以手玩一下,就能发现其中的窍门

其实用数组构建的堆,线段树等,都有类似的规律,父节点和子节点之间,其索引值倍增,偏差1

这题的规律为

向左,a_{i+1}=2 * a_i - 1

向右 a_{i+1}=2 * a_i

自顶向下模拟即可

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 n = sc.nextInt();
        int q = sc.nextInt();
        for (int i = 0; i < q; i++) {
            char[] cmd = sc.next().toCharArray();
            long res = 1;
            for (char c: cmd) {
                if (c == 'L') {
                    res = 2 * res - 1;
                } else {
                    res = 2 * res;
                }
            }
            System.out.println(res);
        }
    }

}

C. 分组

Q: 就是把一个数组分成k组,使得最大的一组高度差最小。

这种最大最小的问题,往往是二分的解法,一般百试不爽,^_^

可以先排序,然后在尝试分组

二分容易,但是核心的check逻辑,该如何组织呢?

假定一个高度差h,然后从左到右贪心,使得每一组成员越多越好,如果最终的组数g<=k, 说明这个高度差满足需求,反之则不行。

import java.io.BufferedInputStream;
import java.util.Arrays;
import java.util.Scanner;

public class Main {

    static boolean check(int[] arr, int m, int k) {
        int i = 0;
        int tk = 0;
        while (i < arr.length) {
            int j = i + 1;
            tk++;
            while (j < arr.length && arr[j] - arr[i] <= m) {
                j++;
            }
            i = j;
        }
        return tk <= k;
    }

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

        Arrays.sort(arr);
        int l = 0, r = arr[n - 1] - arr[0];
        while (l <= r) {
            int m = l + (r - l) / 2;
            if (check(arr, m, k)) {
                r = m - 1;
            } else {
                l = m + 1;
            }
        }
        System.out.println(l);
    }

}

D. 健身

Q: 就是在几个离散的时间区间内,安排一些健身活动(某种健身活动无限续杯),需要完整,求最大的收益和。

健身活动(无数量限制),因此每个区间的最优解彼此独立,互不影响,而且总和就是最大的收益和。

那一个区间,如何求解呢?

本质还是一个

有容量上限的完全背包问题

所以这题的思路是

  • 拆解离散的可用时间片段
  • 完全背包(跑一次就行)
import java.io.*;
import java.util.*;

public class Main {

    public static void main(String[] args) {
        Scanner sc = new Scanner(new BufferedInputStream(System.in));

        int n = sc.nextInt(), m = sc.nextInt(), x = sc.nextInt();
        int[][] ws = new int[m][2];
        List<Integer> arr = new ArrayList<>();
        for (int i = 0; i < x; i++) {
            int b = sc.nextInt();
            arr.add(b);
        }
        for (int i= 0; i < m; i++) {
            ws[i][0] = sc.nextInt();
            ws[i][1] = sc.nextInt();
        }

        // 抽取离散的空闲时间段
        Collections.sort(arr);
        List<Integer> empty = new ArrayList<>();
        for (int i = 0; i < arr.size() - 1; i++) {
            int v = arr.get(i + 1) - arr.get(i) - 1;
            if (v > 0) empty.add(v);
        }
        // 注意头尾
        if (arr.size() > 0) {
            if (arr.get(0) > 1) empty.add(arr.get(0) - 1);
            if (arr.get(arr.size() - 1) < n) empty.add(n - arr.get(arr.size() - 1));
        }

        // 完全背包
        long[] dp = new long[n + 1];
        for (int i = 0; i < m; i++) {
            int s = 1 << ws[i][0];
            for (int j = 0; j + s <= n; j++) {
                dp[j + s] = Math.max(dp[j + s], dp[j] + ws[i][1]);
            }
        }

        // 再来一个前缀预处理
        for (int i = 1; i <= n; i++) {
            dp[i] = Math.max(dp[i], dp[i - 1]);
        }

        long sum = 0;
        for (int e: empty) {
            sum += dp[e];
        }
        System.out.println(sum);
    }

}


E. 契合匹配

Q: 求两个字符串,通过移动,是否可以匹配(相等)

当然这边的匹配,是大写字母匹配小写字母,稍微饶了一下。

这是道很典的字符串板子题

  • KMP
  • Z函数
  • 字符串Hash

这些方法都可以,而且都是O(n)的,不过这里可向左,也可以向右,所以这边需要特别注意下。

这边使用了字符串hash,而且这题单hash可以过,^_^.

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 n = sc.nextInt();
        char[] str1 = sc.next().toCharArray();
        char[] str2 = sc.next().toCharArray();

        for (int i = 0; i < n; i++) {
            if (str2[i] >= 'a' && str2[i] <= 'z') {
                str2[i] = (char)(str2[i] - 'a' + 'A');
            } else {
                str2[i] = (char)(str2[i] - 'A' + 'a');
            }
        }

        long mod = (long)1e9 + 7;
        StringHash hash1 = new StringHash(new String(str1), 13, mod);
        StringHash hash2 = new StringHash(new String(str2), 13, mod);

        int inf = Integer.MAX_VALUE / 10;
        int ans = inf;

        long h1 = hash1.query(0, n - 1);
        if (h1 == hash2.query(0, n - 1)) {
            ans = 0;
        } else {
            for (int i = 0; i < n - 1; i++) {
                long h2 = hash2.rotate(i);
                if (h1 == h2) {
                    ans = Math.min(ans, Math.min((i + 1), n - (i + 1)));
                }
            }
        }
        if (ans == inf) {
            System.out.println("No");
        } else {
            System.out.println("Yes");
            System.out.println(ans);
        }
    }

    static class StringHash {
        char[] str;
        long p, mod;

        int n;
        long[] pre; // hash前缀和
        long[] pow; // p的幂次

        public StringHash(String s, int p, long mod) {
            this.str = s.toCharArray();
            this.p = p;
            this.mod = mod;

            this.n = str.length;
            pre = new long[n + 1];
            pow = new long[n + 1];

            pow[0] = 1;
            for (int i = 1; i <= n; i++) {
                pow[i] = pow[i - 1] * p % mod;
            }
            for (int i = 0; i < str.length; i++) {
                pre[i + 1] = (pre[i] * p % mod + str[i]) % mod;
            }
        }

        long query(int l, int r) {
            long res = pre[r + 1] - pre[l] * pow[r - l + 1] % mod;
            return (res % mod + mod) % mod;
        }

        long rotate(int l) {
            if (l < 0 || l >= str.length - 1) {
                return query(0, str.length - 1);
            } else {
                long h1 = query(0, l);
                long h2 = query(l + 1, str.length - 1);
                return (h2 * pow[l + 1] % mod + h1) % mod;
            }
        }

        static long evaluate(String s, int p, long mod) {
            long h = 0;
            for (char c: s.toCharArray()) {
                h = (h * p % mod + c) % mod;
            }
            return h;
        }
    }
    
}


F. 奇怪的线段

Q: 给定一些区间集合,查询a, b两点,其a在区间,b不在。求集合中,有多少区间符合条件?

区间的问题,可以往二维偏序的解法靠拢

因为查询很多,可以借助离线解法,把查询和被查询区间整合到一起。

这边提供一个离线解法,构建操作序列,并借助树状数组来统计

假设a < b

对于查询(a, b),构建元组

(1, a, b, i), type=1,表示查询

对于区间(x, y), 则构建两个元组

(0, x, y, _), type=0,表示添加区间

(2, y, _, _), type=2, 表示删除区间

按左端点作为一级排序,按操作类型作为二级排序

然后引入树状数组(需要离散化映射),维护右端点的计数

这样天然保证了包含a点,而不包含b点,用树状数组范围查询一下即可。

而对于a > b的情况

需要从右端点的切入并排序

注:这个解法,码量还是有点大,而且必须加快读。更好的解法,见官解:差分+容斥原理

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
import java.util.stream.Collectors;

public class Main {

    static void handle1(int[][] intervals, int[][] queries, int[] res) {

        Set<Integer> range = new TreeSet<>();

        int n = intervals.length;
        List<int[]> ops = new ArrayList<>();

        for (int i = 0; i < n; i++) {
            ops.add(new int[] {intervals[i][0], intervals[i][1], 0, i});
            ops.add(new int[] {intervals[i][1], -1, 2, i});
            range.add(intervals[i][1]);
        }

        // 离线, 在线
        int q = queries.length;
        for (int i = 0; i < q; i++) {
            if (queries[i][0] < queries[i][1]) {
                ops.add(new int[]{queries[i][0], queries[i][1], 1, i});
                range.add(queries[i][1]);
            }
        }

        int ptr = 0;
        Map<Integer, Integer> idMap = new HashMap<>();
        for (int k: range) {
            idMap.put(k, ++ptr);
        }
        int m = idMap.size();
        BIT bit = new BIT(m);

        Collections.sort(ops, Comparator.comparing((int[] x) -> x[0]).thenComparingInt(x -> x[2]));
        for (int[] e: ops) {
            int p = e[2];
            if (p == 0) {
                bit.update(idMap.get(e[1]), 1);
            } else if (p == 1) {
                int cnt = bit.query(idMap.get(e[1]) - 1);
                res[e[3]] = cnt;
            } else {
                bit.update(idMap.get(e[0]), -1);
            }
        }

    }

    static void handle2(int[][] intervals, int[][] queries, int[] res) {

        Set<Integer> range = new TreeSet<>();
        int n = intervals.length;
        List<int[]> ops = new ArrayList<>();

        for (int i = 0; i < n; i++) {
            ops.add(new int[] {intervals[i][1], intervals[i][0], 0, i});
            ops.add(new int[] {intervals[i][0], -1, 2, i});
            range.add(intervals[i][0]);
        }

        // 离线, 在线
        int q = queries.length;
        for (int i = 0; i < q; i++) {
            if (queries[i][0] > queries[i][1]) {
                ops.add(new int[]{queries[i][0], queries[i][1], 1, i});
                range.add(queries[i][1]);
            }
        }

        int ptr = 0;
        Map<Integer, Integer> idMap = new HashMap<>();
        for (int k: range) {
            idMap.put(k, ++ptr);
        }
        int m = idMap.size();
        BIT bit = new BIT(m);

        Collections.sort(ops, Comparator.comparing((int[] x) -> -x[0]).thenComparingInt(x -> x[2]));
        for (int[] e: ops) {
            int p = e[2];
            if (p == 0) {
                bit.update(idMap.get(e[1]), 1);
            } else if (p == 1) {
                int cnt = bit.query(m) - bit.query(idMap.get(e[1]));
                res[e[3]] = cnt;
            } else {
                bit.update(idMap.get(e[0]), -1);
            }
        }
    }

    public static void main(String[] args) {
        AReader sc = new AReader();

        int n = sc.nextInt(), q= sc.nextInt();

        int[][] intervals = new int[n][2];
        for (int i = 0; i < n; i++) {
            intervals[i][0] = sc.nextInt();
            intervals[i][1] = sc.nextInt();
        }

        // 离线, 在线
        int[][] queries = new int[q][2];
        for (int i = 0; i < q; i++) {
            queries[i][0] = sc.nextInt();
            queries[i][1] = sc.nextInt();
        }

        // 离散化 + 离线计算
        int[] res = new int[q];
        handle1(intervals, queries, res);
        handle2(intervals, queries, res);

        System.out.println(Arrays.stream(res).mapToObj(String::valueOf).collect(Collectors.joining("\n")));
    }

    static class BIT {
        int n;
        int[] arr;
        public BIT(int n) {
            this.n = n;
            this.arr = new int[n + 1];
        }

        void update(int p, int d) {
            while (p <= n) {
                this.arr[p] += d;
                p += p & -p;
            }
        }

        int query(int p) {
            int r = 0;
            while (p > 0) {
                r += arr[p];
                p -= p&-p;
            }
            return r;
        }
    }

    static
    class AReader {
        private BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        private StringTokenizer tokenizer = new StringTokenizer("");
        private String innerNextLine() {
            try {
                return reader.readLine();
            } catch (IOException ex) {
                return null;
            }
        }
        public boolean hasNext() {
            while (!tokenizer.hasMoreTokens()) {
                String nextLine = innerNextLine();
                if (nextLine == null) {
                    return false;
                }
                tokenizer = new StringTokenizer(nextLine);
            }
            return true;
        }
        public String nextLine() {
            tokenizer = new StringTokenizer("");
            return innerNextLine();
        }
        public String next() {
            hasNext();
            return tokenizer.nextToken();
        }
        public int nextInt() {
            return Integer.parseInt(next());
        }

        public long nextLong() {
            return Long.parseLong(next());
        }

//        public BigInteger nextBigInt() {
//            return new BigInteger(next());
//        }
        // 若需要nextDouble等方法,请自行调用Double.parseDouble包装
    }

}


写在最后

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值