牛客周赛 Round 21 解题报告 | 珂学家 | 堆栈的妙用

小红的Baidu

https://ac.nowcoder.com/acm/contest/70303/A


前言

alt


整体评价

从A题中的Baidu, 可以猜到这场有几道题来自于百度校招。

其实B题有点意思,如果把十字星的范围放大,那就可以成为一个hard题。

D题也挺意思的,大概有两种思路,一种是从左到右枚举右端点,增量累加,一种是贡献思路。


欢迎关注

珂朵莉 牛客周赛专栏

珂朵莉 牛客小白月赛专栏


A. 小红的Baidu

字符串s,重排后,可以等价于“Baidu”

思路, 最小化表达式

import java.io.*;
import java.util.*;
import java.util.stream.Collectors;

public class Main {

    // Java 对 char数组,比较特殊
    static String expression(String s) {
        return s.chars().sorted().mapToObj(x -> "" + ((char)x)).reduce("", (a, b) -> a + b);
    }

    public static void main(String[] args) {
        // Baidu的最小表达式
        String p1 = expression("Baidu");
        Scanner sc = new Scanner(new BufferedInputStream(System.in));
        int t = sc.nextInt();
        while (t-- > 0) {
            String s = sc.next();
            String p2 = expression(s);
            if (p1.equals(p2)) {
                System.out.println("Yes");
            } else {
                System.out.println("No");
            }
        }
    }

}

B. 小红盖章

因为十字星的影响范围小,所以可以正向模拟即可。

但是如果这题十字星的范围很大,那又该如何求解呢?

这题,可以用逆向思路,即后续操作的优先级最大,后续操作会覆盖前面的。

但是仅有逆向思维是不够的,还需要对范围遍历进行优化,这需要借助数据结构。

  • 构建横向W个链式并查集,纵向H个链式并查集,根据逆序优先,把遍历的代价(4K)降到均摊O(1)
  • treeset, 均摊代价为

这边还是正向解法

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

public class Main {

    static int[][] dirs = new int[][] {
            {-1, 0}, {-2, 0}, {1, 0}, {2, 0},
            {0, -1}, {0, -2}, {0, 1}, {0, 2}
    };

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

        int h = sc.nextInt(), w = sc.nextInt();
        int k = sc.nextInt();

        char[][] grid = new char[h][w];
        for (int i = 0; i < h; i++) {
            Arrays.fill(grid[i], '.');
        }

        // 正向操作
        for (int i = 0; i < k; i++) {
            int y = sc.nextInt() - 1, x = sc.nextInt() - 1;
            char g = sc.next().charAt(0);
            grid[y][x] = g;
            for (int j = 0; j < dirs.length; j++) {
                int y0 = y + dirs[j][0];
                int x0 = x + dirs[j][1];
                if (y0 >= 0 && y0 < h && x0 >= 0 && x0 < w) {
                    grid[y0][x0] = g;
                }
            }
        }

        for (int i = 0; i < h; i++) {
            System.out.println(new String(grid[i]));
        }
    }

}

逆序+链式并查集

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

public class Main {

    // 链式并查集
    static class Dsu {
        int n;
        int[] arr;
        public Dsu(int n) {
            this.n = n + 1;
            this.arr = new int[n + 2];
            Arrays.fill(arr, -1);
        }
        int leader(int u) {
            if (arr[u] == -1) return u;
            return arr[u] = leader(arr[u]);
        }
        // 向右合并
        void merge(int u) {
           int ai = leader(u);
           int bi = leader(ai + 1);
           arr[ai] = bi;
        }
    }


    static class Tx {
        int x, y;
        char s;
        public Tx(int x, int y, char s) {
            this.x = x;
            this.y = y;
            this.s = s;
        }
    }

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

        int h = sc.nextInt(), w = sc.nextInt();
        int k = sc.nextInt();

        char[][] grid = new char[h][w];
        for (int i = 0; i < h; i++) {
            Arrays.fill(grid[i], '.');
        }

        Dsu[] rows = new Dsu[h];
        Arrays.setAll(rows, x -> new Dsu(w));
        Dsu[] cols = new Dsu[w];
        Arrays.setAll(cols, x -> new Dsu(h));

        Tx[] ops = new Tx[k];
        for (int i = 0; i < k; i++) {
            int x = sc.nextInt() - 1, y = sc.nextInt() - 1;
            char g = sc.next().charAt(0);
            ops[i] = new Tx(x, y, g);
        }

        int E = 2; // 十字的边长
        
        // 逆序
        for (int i = k - 1; i >= 0; i--) {
            Tx op = ops[i];
            // 水平方向
            Dsu row = rows[op.x];
            int s = Math.max(op.y - E, 0);
            int e = Math.min(op.y + E, w - 1);
            while (true) {
                int y0 = row.leader(s);
                if (y0 > e) break;
                if (grid[op.x][y0] == '.') {
                    grid[op.x][y0] = op.s;
                }
                row.merge(y0);
            }
            // 垂直方向
            Dsu col = cols[op.y];
            s = Math.max(op.x - E, 0);
            e = Math.min(op.x + E, h - 1);
            while (true) {
                int x0 = col.leader(s);
                if (x0 > e) break;
                if (grid[x0][op.y] == '.') {
                    grid[x0][op.y] = op.s;
                }
                col.merge(x0);
            }
        }

        for (int i = 0; i < h; i++) {
            System.out.println(new String(grid[i]));
        }
    }

}

C. 小红的数组修改

枚举每个位子被修改,然后计算在限制内,可以修改几种方式,能满足需求。

这边有3个限制

  • 被改变的数,必须小于等于p
  • 必须改变,不能为原先的值
  • 改变后,数组和为k的倍数

假设数组累加和S

则改变后

S - arr[i] + y = k * x

取模变换

(arr[i] - S) mod k = y mod k, 且0 < y <= p, y != arr[i]

求得,r = (arr[i] - S) mod k, r为非负整数

转化为 y = k * t + r, 此时 0 < y <= p, 求t的非整数解个数

因为y<=p, 因此个数为 基本盘为 (p - r) / k

但是这里面有两个边界

  • 如果r>0, 则修改方案需要额外 +1
  • 如果arr[i] % k == r, 则需要额外 -1
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();
        int p = sc.nextInt();
        int x = sc.nextInt();

        long acc = 0;
        long[] arr = new long[n];
        for (int i = 0; i < n; i++) {
            arr[i] = sc.nextLong();
            acc += arr[i];
        }

        // 枚举
        long res = 0;
        for (int i = 0; i < n; i++) {
            long left = (acc - arr[i]) % x;

            // r为对应的同余
            long r = (left == 0) ? 0 : (x - left);
            if (p >= r) {
                res += (p - r) / x;
                if (r > 0) res++; // 需要补上1个

                // 需要去除相等的情况
                if (arr[i] <= p && arr[i] % x == r) res--;
            }
        }
        System.out.println(res);
    }

}

D. 括号匹配问题

大致有两种思路,但无一例外都借助stack来实现。

以往做括号相关的题时,往往会引入stack来实现,这次也是如此。

方法1:递推函数

S(i) 为 以i节点为右端点的所有子区间的()配对数

则S(i+1) 和 S(i) 的递进关系如何维护?

  • 如果str[i+1]为'('

那么 S(i+1)=S(i), 即配对数完全复制,但是没有新增加

  • 如果str[i+1]为’)‘

那这个’)', 可与左侧多余的’(‘构建一个新的配对关系,那具体能贡献多少个新增呢? 这又如何维护呢?

其实可以手玩一下,就能发现,在i处增加一个'(', 相当于stack push(i+1)个,而消耗匹配一个,相当于stack pop栈顶。

因此 S(i + 1) = S(i) + 2 * stack.pop()

而最终的结果为

import java.io.BufferedInputStream;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Scanner sc = new Scanner(new BufferedInputStream(System.in));
        char[] str = sc.next().toCharArray();
        int n = str.length;

        long res = 0;
        long s = 0;
        Deque<Integer> stack = new ArrayDeque<>();

        // 可能要数据结构辅助
        for (int i = 0; i < n; i++) {
            char c = str[i];
            if (c == '(') {
                stack.push((i + 1));
            } else if (c == ')') {
                if (!stack.isEmpty()) {
                    int v = stack.pop();
                    s += 2l * v;
                }
            }
            res += s;
        }
        System.out.println(res);
    }

}

方法2:贡献法

看了下前排大佬,基本都是贡献法

就是找到一个新()匹配, 其可以在多少个区间中重复贡献。

也是基于stack

匹配的时候,

import java.io.BufferedInputStream;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Scanner sc = new Scanner(new BufferedInputStream(System.in));
        char[] str = sc.next().toCharArray();
        int n = str.length;

        long res = 0;
        Deque<Integer> stack = new ArrayDeque<>();

        // 可能要数据结构辅助
        for (int i = 0; i < n; i++) {
            char c = str[i];
            if (c == '(') {
                stack.push((i + 1));
            } else if (c == ')') {
                if (!stack.isEmpty()) {
                    int v = stack.pop();
                    res += 2l * v * (n - i);
;               }
            }
        }
        System.out.println(res);
    }

}


写在最后

alt

牛客周赛解题报告系列 文章被收录于专栏

牛客周赛,定位求职笔试真题,倾向于面试算法

全部评论
感谢佬
2
送花
回复
分享
发布于 2023-11-27 00:19 湖南
其实只有第一题是百度的,现在周赛一般是混搭很多公司选(之前的美团和携程除外),这样也能增加选手去网上搜原题的成本
1
送花
回复
分享
发布于 2023-11-27 12:19 北京
蔚来
校招火热招聘中
官网直投
请问d题题意是什么,我都没看懂题意
1
送花
回复
分享
发布于 2023-11-27 20:54 四川
请问B题链式并查集怎么写,有点思路但是很模糊
1
送花
回复
分享
发布于 2023-11-28 20:58 吉林
太强了
1
送花
回复
分享
发布于 2023-11-30 00:18 湖北
如果r>0, 则修改方案需要额外 +1 为什么 orz
1
送花
回复
分享
发布于 05-22 13:07 河北

相关推荐

10 2 评论
分享
牛客网
牛客企业服务