第10届蓝桥杯JavaB组省赛

第10届蓝桥杯JavaB组省赛

其他链接

第9届蓝桥杯JavaB组省赛 - Cattle_Horse

第11届蓝桥杯JavaB组省赛 - Cattle_Horse

第12届蓝桥杯JavaB组省赛 - Cattle_Horse

第13届蓝桥杯javaB组省赛 - Cattle_Horse

前言

用时及分数

未进行用时测试

感受

看清楚题目!!!

\(B\) 题要求字符串为非空连续字串

\(D\) 题要求分解的三个数各不相同

收获

复习了 \(BFS\) 的使用及其路径的输出

初次学习了数位 \(DP\)

复习了单调队列(滑动窗口)

系统的认识了后缀表达式

学习了\(Java\)的一些排序方法

试题 A 组队

问题描述

作为篮球队教练,你需要从名单中选出 \(1\) 号位至 \(5\) 号位各一名球员,组成球队的首发阵容。
每位球员担任 \(1\) 号位至 \(5\) 号位时的评分表team.txt中。请你计算首发阵容 \(1\) 号位至 \(5\) 号位的评分之和最大可能是多少?

答案:\(490\)

Code

\(DFS\) 遍历每个位置由谁担任

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Main {
    static int ans = 0;
    static boolean[] book = new boolean[21];
    static int[][] a = new int[21][6];

    static void dfs(int now, int sum) {
        if (now == 5) {
            ans = Math.max(ans, sum);
            return;
        }
        for (int i = 1; i <= 20; ++i) {
            if (book[i]) continue;
            book[i] = true;
            dfs(now + 1, sum + a[i][now]);
            book[i] = false;
        }
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader(".//team.txt"));
        for (int i = 1; i <= 20; ++i) {
            String[] num = br.readLine().split(" ");
            for (int j = 1; j <= 5; ++j) {
                a[i][j] = Integer.parseInt(num[j]);
            }
        }
        for (int i = 1; i <= 20; ++i) {
            dfs(1, a[i][1]);
        }
        System.out.println(ans);//490
    }
}

试题 B 不同子串

问题描述

一个字符串的非空子串是指字符串中长度至少为 \(1\)连续的一段字符组成的串。例如,字符串aaab 有非空子串 a, b, aa, ab, aaa, aab, aaab,一 共 \(7\) 个。
注意在计算时,只算本质不同的串的个数。
请问,字符串 0100110001010001 有多少个不同的非空子串

答案:\(100\)

Code

双重循环 + 去重

import java.util.HashSet;

public class Main {
    static HashSet<String> set = new HashSet<>();

    static String s = "0100110001010001";

    public static void main(String[] args) {
        int len = s.length();
        for (int i = 0; i < len; ++i) {
            for (int j = i; j < len; ++j) {
                // substring是左闭右开区间
                set.add(s.substring(i, j + 1));
            }
        }
        System.out.println(set.size());//100
    }
}

试题 C 数列求值

问题描述

给定数列 1, 1, 1, 3, 5, 9, 17, …,从第 4 项开始,每项都是前 3 项的和。求第 20190324 项的最后 4 位数字。

答案:\(4659\)

思路

方法:

  1. 使用 \(BigInteger\) 模拟
  2. 最后四位数字相当于对 \(10000\) 取模(可滚动数组优化)
public class Main {
    static final int MOD = (int) 1e4;
    static final int N = 20190324;


    public static void main(String[] args) {
        int[] arr = new int[4];
        final int n = 4;
        arr[1] = arr[2] = arr[3] = 1;
        int now = 4;
        while (now <= N) {
            arr[now % n] = (arr[(now - 1) % n] + arr[(now - 2) % n] + arr[(now - 3) % n]) % MOD;
            ++now;
        }
        System.out.println(arr[N % n]);//4659
    }
}

试题 D 数的分解

问题描述

把 2019 分解成 3 个各不相同的正整数之和,并且要求每个正整数都不包含数字 2 和 4,一共有多少种不同的分解方法?
注 意 交 换 3 个 整 数 的 顺 序 被 视 为 同 一 种 方 法, 例 如 1000+1001+18 和 1001+1000+18 被视为同一种。

答案:\(40785\)

思路

是否包含 \(2\)\(4\) 可用数字循环除 \(10\)\(10\) 来判断

交换顺序被视为同一种方法,使三个数递增的遍历就不会出现重复情况

循环

public class Main {
    static final int n = 2019;

    static boolean check(int x) {
        while (x != 0) {
            if (x % 10 == 2 || x % 10 == 4) return true;
            x /= 10;
        }
        return false;
    }

    public static void main(String[] args) {
        int ans = 0;
        for (int i = 1; i <= n / 3; ++i) {
            if (check(i)) continue;
            for (int j = i + 1; j <= n; ++j) {
                int k = n - i - j;
                if (k <= j) break;
                if (check(j)) continue;
                if (check(k)) continue;
                ++ans;
            }
        }
        System.out.println(ans);// 40785
    }
}

DFS

当分解个数不为 \(3\) 个,而为一个输入数 \(k\) 或者 分解个数很大时,就需要通过 \(DFS\) 来选择了

同样的,为了不出现重复,\(k\) 个数需要递增的选择

public class Main {
    static final int n = 2019;
    static final int k = 3;

    static boolean check(int x) {
        while (x != 0) {
            if (x % 10 == 2 || x % 10 == 4) return true;
            x /= 10;
        }
        return false;
    }

    static int ans = 0;

    // 目前选了now个数,上一个选择的数是num,已经选择的数的和是sum
    static void dfs(int now, int num, int sum) {
        // 选择前k-1个数时,最后一个数随之确定
        if (now == k - 1) {
            if (!check(n - sum)) ++ans;
            return;
        }
        // 保证下一个数足够比 i 大
        for (int i = num + 1; i < n - sum - i; ++i) {
            if (check(i)) continue;
            dfs(now + 1, i, sum + i);
        }
    }

    public static void main(String[] args) {
        dfs(0, 0, 0);
        System.out.println(ans);// 40785
    }
}

试题 E 迷宫

问题描述

下图给出了一个迷宫的平面图,其中标记为 1 的为障碍,标记为 0 的为可以通行的地方。

010000 
000100 
001001 
110000

迷宫的入口为左上角,出口为右下角,在迷宫中,只能从一个位置走到这个它的上、下、左、右四个方向之一。
对于上面的迷宫,从入口开始,可以按DRRURRDDDR 的顺序通过迷宫,一共 10 步。其中 D、U、L、R 分别表示向下、向上、向左、向右走。
对于更复杂的迷宫(30 行 50 列 见文件maze.txt),请找出一种通过迷宫的方式,其使用的步数最少,在步数最少的前提下,请找出字典序最小的一个作为答案。
请注意在字典序中 D<L<R<U。

答案:DDDDRRURRRRRRDRRRRDDDLDDRDDDDDDDDDDDDRDDRRRURRUURRDDDDRDRRRRRRDRRURRDDDRRRRUURUUUUUUULULLUUUURRRRUULLLUUUULLUUULUURRURRURURRRDDRRRRRDDRRDDLLLDDRRDDRDDLDDDLLDDLLLDLDDDLDDRRRRRRRRRDDDDDDRR

思路

思路一:\(DFS\) 遍历所有情况,记录所有能到达终点的步骤,最后取步数最少和字典序最少的步骤

思路二:\(BFS\) 求最短路径,对于字典序最小,可以在每次扩展遍历时,优先选择字典序小的步骤(按 \(D<L<R<U\)

输出路径需要记录当前的节点是通过哪个步骤过来的,最后递归从终点找起点

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Queue;

public class Main {
    //按照  DLRU  的顺序bfs,先到的就是步数最少且字典序最小的
    static final char[] direction = "DLRU".toCharArray();
    static final int[] dx = {1, 0, 0, -1}, dy = {0, -1, 1, 0};

    static char[][] map;//迷宫
    static final int n = 30, m = 50;
    static boolean[][] vis = new boolean[n][m];//标记该点是否走过
    static char[][] Path = new char[n][m]; //记录该点是通过什么步骤过来的
    static StringBuilder ans = new StringBuilder();

    // 从终点dfs回头找起点
    static void dfsPath(int x, int y) {
        if (x == 0 && y == 0) return;
        if (Path[x][y] == 'D') dfsPath(x - 1, y);//原来向下走,现在向上走
        else if (Path[x][y] == 'L') dfsPath(x, y + 1);
        else if (Path[x][y] == 'R') dfsPath(x, y - 1);
        else dfsPath(x + 1, y);
        ans.append(Path[x][y]);
    }

    static void bfs() {
        Queue<Point> q = new LinkedList<>();
        q.add(new Point(0, 0));
        vis[0][0] = true;
        while (!q.isEmpty()) {
            Point p = q.poll();
            int x = p.x, y = p.y;
            for (int i = 0; i < 4; ++i) {
                int nx = x + dx[i], ny = y + dy[i];
                if (check(nx, ny)) {
                    vis[nx][ny] = true;
                    Path[nx][ny] = direction[i];
                    if (nx == n - 1 && ny == m - 1) return;
                    q.add(new Point(nx, ny));
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new FileReader(".//maze.txt"));
        map = new char[n][];
        for (int i = 0; i < n; ++i) map[i] = in.readLine().toCharArray();
        bfs();
        dfsPath(n - 1, m - 1);
        System.out.println(ans);
    }


    //检查该点是否可以走
    static boolean check(int x, int y) {
        return x < n && x >= 0 && y < m && y >= 0 && map[x][y] != '1' && !vis[x][y];
    }
}

class Point {
    int x, y;

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

试题 F 特别数的和

问题描述

小明对数位中含有 2、0、1、9 的数字很感兴趣(不包括前导 0),在 1 到 40 中这样的数包括 1、2、9、10 至 32、39 和 40,共 28 个,他们的和是 574。
请问,在 1 到 n 中,所有这样的数的和是多少?
【输入格式】
输入一行包含两个整数 n。
【输出格式】
输出一行,包含一个整数,表示满足条件的数的和。

【样例输入】
40
【样例输出】
574

【评测用例规模与约定】
对于 20% 的评测用例,1 ≤ n ≤ 10。
对于 50% 的评测用例,1 ≤ n ≤ 100。
对于 80% 的评测用例,1 ≤ n ≤ 1000。
对于所有评测用例,1 ≤ n ≤ 10000。

思路

枚举模拟

枚举 \(1\sim n\) ,对每个数进行判断是否含有 \(2,0,1,9\),判断方法与 \(D\) 题相同

时间复杂度:\(O(\sum_{i=1}^{n}len(i)=nlogn)\)

import java.util.Scanner;

public class Main {
    static boolean check(int x) {
        while (x != 0) {
            int t = x % 10;
            if (t == 2 || t == 0 || t == 1 || t == 9) return true;
            x /= 10;
        }
        return false;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        final int n = sc.nextInt();
        long ans = 0;
        for (int i = 1; i <= n; ++i) {
            if (check(i)) ans += i;
        }
        System.out.println(ans);
    }
}

数位DP

从高位到低位选择数字,每个数的权重与其所在位置有关

如数 \(2???\),目前已经含有 \(2\) 了,后面的数无论选择什么,都满足条件。

因此只需计算出后面 \(0\sim 9\) 的数量,乘上其所在位置的权重,即有多少个数是满足条件且形如 \(2???\) 这种形式的数

\[\begin{aligned} 高度为h的满n叉树的节点个数 &= 公比为\ 每个节点的分支数\ 项数为\ 树的高度\ 的等比数列之和\\ &=1\times\dfrac{1-n^h}{1-n}\\ &=\dfrac{n^h-1}{n-1} \end{aligned} \]

\[\begin{aligned} 时间复杂度&=递归次数\times每次递归的时间复杂度\\ &=高度为len(n)的满10叉树的节点个数\times10\\ &=\dfrac{10^{log_{10}n}-1}{10-1}\times 10\\ &=\dfrac{10\times(n-1)}{9} \end{aligned} \]

import java.util.Scanner;

public class Main {
    static int[] POW10;//10的i次方
    static int[] LIMIT;//第i位的限制,即n的分解

    //计算 n 的位数
    static int digit(int n) {
        return (int) Math.log10(n) + 1;
    }

    static boolean check(int x) {
        return x == 2 || x == 0 || x == 1 || x == 9;
    }

    /**
     * now   : 目前要选择第几位
     * flag  : 是否已经含有2,0,1,9
     * have  : 该位置前面是否填了数字 (即是否为最高位)
     * limit : 该位置是否被限制(高位的数是否紧贴上界)
     * 返回值 : 该位置的后面的数的方案数及总和
     **/
    static int[] dfs(int now, boolean flag, boolean limit, boolean have) {
        // 选择完了
        if (now == 0) {
            if (flag) return new int[]{1, 0};
            return new int[]{0, 0};
        }
        int[] ans = new int[2];
        // 如果前面没有填数字,可以跳过
        if (!have) ans = dfs(now - 1, false, false, false);
        int high = limit ? LIMIT[now] : 9;
        int low = have ? 0 : 1;
        for (int i = high; i >= low; --i) {
            int[] t = dfs(now - 1, flag || check(i), limit && i == high, true);
            ans[0] += t[0];
            ans[1] += i * POW10[now - 1] * t[0] + t[1];
        }
        return ans;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int len = digit(n);
        POW10 = new int[len];
        POW10[0] = 1;
        for (int i = 1; i < len; ++i) POW10[i] = POW10[i - 1] * 10;
        LIMIT = new int[len + 1];
        for (int i = 1, t = n; i <= len; ++i, t /= 10) LIMIT[i] = t % 10;
        int[] ans = dfs(len, false, true, false);
        System.out.println(ans[1]);
    }
}

可以发现上述过程中有大量重复计算。

以求 \(1\sim 987\) 举例:

当计算 \(9??\) 时,目前已经满足含有 \(2,0,1,9\) 了,后两位可以任取,但不能超过上界

当计算 \(8??\) 时,后面的数需要含有 \(2,0,1,9\),无其他限制

当计算 \(7??\) 时,后面的数需要含有 \(2,0,1,9\),无其他限制

当计算 \(6??\) 时,后面的数需要含有 \(2,0,1,9\),无其他限制

当计算 \(5??\) 时,后面的数需要含有 \(2,0,1,9\),无其他限制

当计算 \(4??\) 时,后面的数需要含有 \(2,0,1,9\),无其他限制

当计算 \(3??\) 时,后面的数需要含有 \(2,0,1,9\),无其他限制

当计算 \(2??\) 时,目前已经满足含有 \(2,0,1,9\) 了,后两位可以任取

  • 则其方案数为 \(100\),当前这一位置对答案的贡献(乘上其权重)为 \(2\times100\times100\),可以 \(O(1)\) 计算
  • 后面两位的贡献同样为固定的 \(0\sim99\)\(\dfrac{(0+99)\times 100}{2}\),可以 \(O(1)\) 计算

当计算 \(1??\) 时,目前已经满足含有 \(2,0,1,9\) 了,后两位可以任取,同上

可以发现 \(3\sim 8\),的情况相同,且不能 \(O(1)\) 计算,考虑记忆化存储其方案数和对答案的贡献

这就变成了 数位 \(DP\)

时间复杂度:等于状态个数 \(\times\) 转移个数

状态个数(\(flag\)\(dp\))为:\(len(n)\times 2=log(n)\times 2\)

转移(循环)个数为:每个位置取值范围 \(0\sim 9\)

因此时间复杂度为: \(O(20logn)\)

import java.util.Arrays;
import java.util.Scanner;

public class Main {
    static int[] POW10;//10的i次方
    static int[] LIMIT;//第i位的限制,即n的分解

    //计算 n 的位数
    static int digit(int n) {
        return (int) Math.log10(n) + 1;
    }

    static boolean check(int x) {
        return x == 2 || x == 0 || x == 1 || x == 9;
    }

    //在不受约束时,当前位置及其后面的方案数 为pow10
    //在不受约束时, 当前位置及其后面 任意方案数对答案的贡献 为 (首项+尾项)*项数/2 同样可以 O(1)处理出来
    //dp1: 在不受约束时, 当前位置及其后面 含有2019的方案数
    //dp2: 在不受约束时, 当前位置及其后面 含有2019的方案数对答案的贡献
    //最高位及紧贴上界的不会进行记忆化,因为是非多次出现的情况
    static int[] dp1, dp2;

    /**
     * now   : 目前要选择第几位
     * flag  : 是否已经含有2,0,1,9
     * have  : 该位置前面是否填了数字 (即是否为最高位)
     * limit : 该位置是否被限制(高位的数是否紧贴上界)
     * 返回值 : 该位置的后面的数的方案数及总和
     **/
    static int[] dfs(int now, boolean flag, boolean limit, boolean have) {
        // 选择完了
        if (now == 0) {
            if (flag) return new int[]{1, 0};
            return new int[]{0, 0};
        }
        if (!limit && have) {
            if (flag) return new int[]{POW10[now], (POW10[now] - 1) * POW10[now] / 2};
            if (dp1[now] != -1) return new int[]{dp1[now], dp2[now]};
        }
        int[] ans = new int[2];
        if (!have) ans = dfs(now - 1, false, false, false);
        int high = limit ? LIMIT[now] : 9;
        int low = have ? 0 : 1;
        for (int i = high; i >= low; --i) {
            int[] t = dfs(now - 1, flag || check(i), limit && i == high, true);
            ans[0] += t[0];
            ans[1] += i * POW10[now - 1] * t[0] + t[1];
        }

        if (!limit && have) {
            dp1[now] = ans[0];
            dp2[now] = ans[1];
        }

        return ans;
    }


    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int len = digit(n);
        POW10 = new int[len];
        POW10[0] = 1;
        for (int i = 1; i < len; ++i) POW10[i] = POW10[i - 1] * 10;
        LIMIT = new int[len + 1];
        for (int i = 1, t = n; i <= len; ++i, t /= 10) LIMIT[i] = t % 10;
        dp1 = new int[len];
        dp2 = new int[len];
        Arrays.fill(dp1, -1);
        int[] ans = dfs(len, false, true, false);
        System.out.println(ans[1]);
    }
}

试题 G 外卖店优先级

问题描述

“饱了么”外卖系统中维护着 N 家外卖店,编号 1 ∼ N。每家外卖店都有一个优先级,初始时 (0 时刻) 优先级都为 0。
每经过 1 个时间单位,如果外卖店没有订单,则优先级会减少 1,最低减到 0;而如果外卖店有订单,则优先级不减反加,每有一单优先级加 2。
如果某家外卖店某时刻优先级大于 5,则会被系统加入优先缓存中;如果优先级小于等于 3,则会被清除出优先缓存。
给定 T 时刻以内的 M 条订单信息,请你计算 T 时刻时有多少外卖店在优先缓存中。

【输入格式】
第一行包含 3 个整数 N、 M 和 T 。
以下 M 行每行包含两个整数 t s 和 id,表示 t s 时刻编号 id 的外卖店收到一个订单。

【输出格式】
输出一个整数代表答案。

【样例输入】
2 6 6
1 1
5 2
3 1
6 2
2 1
6 2
【样例输出】
1

【样例解释】
6 时刻时,1 号店优先级降到 3,被移除出优先缓存;2 号店优先级升到 6,加入优先缓存。所以是有 1 家店 (2 号) 在优先缓存中。

【评测用例规模与约定】
对于 80% 的评测用例,1 ≤ N, M, T ≤ 10000。
对于所有评测用例,1 ≤ N, M, T ≤ 100000,1 ≤ ts ≤ T ,1 ≤ id ≤ N。

思路

将输入数据按照先 \(id\)\(ts\) 的规则排序

双指针遍历同一 \(id\) 的外卖单

如果相邻外卖单的 \(ts\) 不同,则其之间所扣除的优先度为 \(ts_2-ts1-1\)

如果相邻外卖单的 \(ts\) 相同,则不扣除优先度

排序时间复杂度为 \(O(mlogm)\),双指针遍历时间复杂度为 \(O(m)\)

则总时间复杂度为 \(O(mlogm)\),空间复杂度为 \(O(m)\)

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

public class Main {
    static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));

    static int get() throws IOException {
        in.nextToken();
        return (int) in.nval;
    }

    public static void main(String[] args) throws IOException {
        int n = get(), m = get(), t = get();
        Pair[] pair = new Pair[m];
        for (int i = 0; i < m; ++i) pair[i] = new Pair(get(), get());
        Arrays.sort(pair);
        int cnt = 0;
        for (int l = 0, r = 0; l < m; l = r) {
            boolean mark = false;
            int id = pair[l].id;
            int val = 0;
            while (r < m && pair[r].id == id) {
                // 先减在加
                if (r != l && pair[r].ts != pair[r - 1].ts) {
                    val = Math.max(0, val - (pair[r].ts - pair[r - 1].ts - 1));
                    if (mark && val <= 3) mark = false;
                }
                val += 2;
                if (val > 5) mark = true;
                ++r;
            }
            if (pair[r - 1].ts != t) {
                val -= t - pair[r - 1].ts;
                if (mark && val <= 3) mark = false;
            }
            if (mark) ++cnt;
        }
        System.out.println(cnt);
    }
}

class Pair implements Comparable<Pair> {
    int ts, id;

    public Pair(int ts, int id) {
        this.ts = ts;
        this.id = id;
    }

    @Override
    public int compareTo(Pair o) {
        if (id != o.id) return id - o.id;
        return ts - o.ts;
    }
}

试题 H 人物相关性分析

问题描述

小明正在分析一本小说中的人物相关性。他想知道在小说中 AliceBob 有多少次同时出现。

更准确的说,小明定义 AliceBob 同时出现的意思是:在小说文本中 AliceBob 之间不超过 K个字符。
例如以下文本:
This is a story about Alice and Bob. Alice wants to send a private message to Bob.
假设 \(K=20\),则 AliceBob 同时出现了 \(2\) 次,分别是 Alice and BobBob. Alice。前者 AliceBob 之间有 \(5\) 个字符,后者有 \(2\) 个字符。
注意:

  1. AliceBob 是大小写敏感的,alicebob 等并不计算在内。
  2. AliceBob 应为单独的单词,前后可以有标点符号和空格,但是不能有字母。例如 Bobbi 并不算出现了 Bob

【输入格式】
第一行包含一个整数 \(K\)
第二行包含一行字符串,只包含大小写字母、标点符号和空格。长度不超过 \(1000000\)

【输出格式】
输出一个整数,表示 AliceBob 同时出现的次数。

【样例输入】
20
This is a story about Alice and Bob. Alice wants to send a private message to Bob.
【样例输出】
2

【评测用例规模与约定】

对于所有评测用例,\(1 ≤ K ≤ 1000000\)

思路

  1. 循环记录每个 \(Alice\)\(Bob\) 出现的位置
  2. 查找位于 \(Alice\) 前和后,且间隔不超过 \(K\) 个字符的 \(Bob\) 个数

时间复杂度为 \(O(nlogn)\)

最终结果会整型溢出,使用 \(long\)

二分

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {
    static final char[] Alice = "Alice".toCharArray();
    static final char[] Bob = "Bob".toCharArray();
    // 记录第i+1个Alice的起始位置
    static int[] a = new int[1000000];
    static int[] b = new int[1000000];
    static char[] s;
    static int len, k;

    static boolean check(int l, int r) {
        return (l == 0 || s[l - 1] == ' ' || s[l - 1] == '.') && (r == len || s[r] == ' ' || s[r] == '.');
    }


    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        k = Integer.parseInt(in.readLine());
        s = in.readLine().trim().toCharArray();
        len = s.length;
        int totA = 0, totB = 0;

        for (int l = 0, r = 0; l < len; l = r) {
            if (s[l] == Alice[r - l]) {
                while (r - l < Alice.length && r < len) {
                    if (s[r] == Alice[r - l]) ++r;
                    else break;
                }
                if (r - l == Alice.length && check(l, r))
                    a[totA++] = l;
            } else if (s[l] == Bob[r - l]) {
                while (r - l < Bob.length && r < len) {
                    if (s[r] == Bob[r - l]) ++r;
                    else break;
                }
                if (r - l == Bob.length && check(l, r))
                    b[totB++] = l;
            } else ++r;
        }
        if (totA == 0 || totB == 0) {
            System.out.println(0);
            return;
        }
        long ans = 0;
        // 在b中 找 a[i] 前后间隔k个字符内的Bob
        for (int i = 0; i < totA; ++i) {
            // 二分找左边界
            int l = 0, r = totB - 1;
            while (l < r) {
                int mid = l + r >> 1;
                if (a[i] - (b[mid] + Bob.length) <= k) r = mid;
                else l = mid + 1;
            }
            if (a[i] - (b[l] + Bob.length) > k) break;
            // 二分找右边界
            int low = l, high = totB - 1;
            while (low < high) {
                int mid = low + high + 1 >> 1;
                if (b[mid] - (a[i] + Alice.length) <= k) low = mid;
                else high = mid - 1;
            }
            if (a[i] - (b[low] + Bob.length) > k) break;
            ans += low - l + 1;
        }
        System.out.println(ans);
    }
}

滑动窗口

滑动窗口是用来维护目前元素与已存入元素的区间关系

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {
    static final char[] Alice = "Alice".toCharArray();
    static final char[] Bob = "Bob".toCharArray();
    // 记录第i+1个Alice的起始位置
    static int[] a = new int[1000000];
    static int[] b = new int[1000000];
    static char[] s;
    static int len, k;

    static boolean check(int l, int r) {
        return (l == 0 || s[l - 1] == ' ' || s[l - 1] == '.') && (r == len || s[r] == ' ' || s[r] == '.');
    }


    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        k = Integer.parseInt(in.readLine());
        s = in.readLine().trim().toCharArray();
        len = s.length;
        int totA = 0, totB = 0;

        for (int l = 0, r = 0; l < len; l = r) {
            if (s[l] == Alice[r - l]) {
                while (r - l < Alice.length && r < len) {
                    if (s[r] == Alice[r - l]) ++r;
                    else break;
                }
                if (r - l == Alice.length && check(l, r))
                    a[totA++] = l;
            } else if (s[l] == Bob[r - l]) {
                while (r - l < Bob.length && r < len) {
                    if (s[r] == Bob[r - l]) ++r;
                    else break;
                }
                if (r - l == Bob.length && check(l, r))
                    b[totB++] = l;
            } else ++r;
        }

        if (totA == 0 || totB == 0) {
            System.out.println(0);
            return;
        }
        long ans = 0;

        // l 和 r 两个变量模拟双端队列
        // 求位于 第i个Alice 前后 k 个字符内的 Bob 的个数
        for (int i = 0, l = 0, r = -1; i < totA; ++i) {
            while (l <= r && a[i] - (b[l] + Bob.length) > k)// 维护左边界,出框的出队
                ++l;//出框则出队
            while (r + 1 < totB && b[r + 1] <= a[i] + Alice.length + k)// 维护右边界,满足条件的入队
                ++r;
            ans += r - l + 1;
        }

        System.out.println(ans);
    }
}

试题 I 后缀表达式

问题描述

给定 \(N\) 个加号、 \(M\) 个减号以及 \(N + M + 1\) 个整数 \(A_1,A_2,\cdots, A_{N+ M+1}\),小明想知道在所有由这 \(N\) 个加号、 \(M\) 个减号以及 \(N + M + 1\) 个整数凑出的合法的
后缀表达式中,结果最大的是哪一个?
请你输出这个最大的结果。
例如使用1 2 3 + -,则 2 3 + 1 - 这个后缀表达式结果是 \(4\),是最大的。
【输入格式】
第一行包含两个整数 \(N\)\(M\)
第二行包含 \(N+M+1\) 个整数 \(A_1,A_2,\cdots,A_{N+ M+1}\)
【输出格式】
输出一个整数,代表答案。

【样例输入】
1 1
1 2 3
【样例输出】
4

【评测用例规模与约定】
对于所有评测用例,\(0 ≤ N, M ≤ 100000\)\(−10^9≤ A_i≤10^9\)

思路

后缀表达式是计算机以栈进行运算的顺序(隐藏括号)

因为只需要输出最大结果,实际上这题与其没太大关系

只有加减号难度会小很多

数字比符号多一个,所以会有一个数字不能进行符号变化

因此选择一个最大的数 \(max\),让其他数在这个数的基础上进行加减

大致形式如下: \(max+(..x..)-(..y..)\)

其余数字会通过符号进行变化,但是无论如何变化,数字只有正负两种情况

  • 如果它是正数,我们将其放到 \(x\)
  • 如果它是负数,我们将其放到 \(y\)

这样可以保证每加入一个数,都使结果变大。

举个例子:

\(2\)\(+\) 号,\(1\)\(-\) 号,求 \(-3,-2,1,5\) 的最大值

有的人可能会想 \(5+1+(-2)-(-3)=7\)

忽略了 \(-(-x-y)=x+y\) 这个性质

正确的应该是:\(5-((-2)+(-3))+1=11\)

上述情况的前提是至少有一个减号,且 \(y\) 处至少要有一个数(因为负号是用掉的,而 \(x\) 处可以没有数,正号相对应的省略)

\(y\) 处至少有的那一个数应该是哪个数呢?

  • 当最小值是负数时,这个负数一定绝对值最大的负数,当减去这个负数时,就变成了加。因此,选择这个最小值放到 \(y\) 处。
  • 当最小值是非负数时,这个非负数的值一定是最小的,就利用这个最小的来提供负号。

综上, \(y\) 处至少要有的这个数应该是最小的数

当没有减号时,所有数字只能进行累加

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

public class Main {
    static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));

    static int get() throws IOException {
        in.nextToken();
        return (int) in.nval;
    }

    public static void main(String[] args) throws IOException {
        int n = get(), m = get();
        int len = n + m + 1;
        int[] a = new int[len];
        for (int i = 0; i < len; ++i) a[i] = get();
        if (m == 0) {
            long sum = 0;
            for (int i = 0; i < len; ++i) sum += a[i];
            System.out.println(sum);
            return;
        }
        Arrays.sort(a);
        long sum = a[len - 1] - a[0];
        for (int i = 1; i < len - 1; ++i) {
            if (a[i] < 0) sum -= a[i];
            else sum += a[i];
        }
        System.out.println(sum);
    }
}
posted @ 2023-01-30 22:49  Cattle_Horse  阅读(52)  评论(0编辑  收藏  举报