牛客周赛 Round 87

写在前面

比赛地址:https://ac.nowcoder.com/acm/contest/105623

因为各种原因,赛时全程用的 java,妈的虽然都会但是手太生了进而影响脑子速度直接倒闭,周赛都 AK 不了了呃呃。

以下记一点 java 的算法题语法。

A

水题,直接做。

java Scanner:

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        Integer[] a = new Integer[3];
        for (int i = 0; i < 3; i++) {
            a[i] = sc.nextInt();
        }
        System.out.println((a[0] > a[1] && a[1] < a[2]) ? "YES" : "NO");
    }
}

B

水题,直接枚举分界即可。

java Scanner:

  • 换行符问题:在使用 next() 和 nextLine() 方法时,需要注意换行符的处理。next() 方法不会消耗换行符,而 nextLine() 会读取直到换行符。因此,在连续调用这两个方法时,可能需要调用一个额外的 nextLine() 来消耗掉 next() 留下的换行符。

String.substring(beginIndex, endIndex)

  • 得到子串 String[beginIndex, endIndex - 1]
  • beginIndex 为起始下标;
  • endIndex 为结束下标(不包含 endIndex)。

Integer.parseInt(@NotNull String s, int radix)

  • s 转换为 int
  • radix 为解析 s 的进制;
import java.util.Scanner;

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

        int n = Integer.parseInt(sc.nextLine());
        /*
        int n = sc.nextInt();
        sc.nextLine();
        */

        for (int t = 0; t < n; t++) {
            String a = sc.nextLine();
            int len = a.length();
            int ans = 0;
            for (int i = 0; i < len - 1; i++) {
                int x = Integer.parseInt(a.substring(0, i + 1), 10);
                int y = Integer.parseInt(a.substring(i + 1, len), 10);
                ans = Math.max(ans, x + y);
            }
            System.out.println(ans);
        }
    }
}

C

妈的爆 int 了查了一万年没查出来,唐。

发现仅关注每个数的符号,考虑把正数负数分别视为 -1/1,则显然有如下策略最优:

  • s[i] == '<',仅需保证 \(a_i\) 为负数;
  • s[i] == '>',仅需保证 \(a_i\) 为正数;
  • s[i] == 'Z',仅需保证 \(a_i, a_{i - 1}\) 同号,若需要修改仅需令 \(a_i:=a_{i - 1}\) 即可。

直接模拟即可。

注意原数列中两个数直接相乘可能爆 int 呃呃。

import java.util.Scanner;

public class Main {
    public static Scanner sc;
    public static long[] arr;
    public static String s;

    public static void solve() {
        int n = sc.nextInt();
        sc.nextLine();

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

        s = sc.nextLine();

        int ans = 0;
        for (int i = 0; i < n; i++) {
            if (s.charAt(i) == '<') {
                if (arr[i] >= 0) {
                    ++ ans;
                }
                arr[i] = -1;
            } else if (s.charAt(i) == '>') {
                if (arr[i] <= 0) {
                    ++ ans;
                }
                arr[i] = 1;
            } else if (s.charAt(i) == 'Z') {
                if (arr[i] * arr[i - 1] <= 0) {
                    ++ ans;
                }
                arr[i] = arr[i - 1];
            }
        }
        System.out.println(ans);
    }

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

        int T = sc.nextInt();
        sc.nextLine();

        for (int t = 0; t < T; t++) {
            solve();
        }
    }
}
/*

1
10
1 1 1 -1 -1 -1 1 1 1 1
>>>ZZZ>>>>

*/

D

先特判 \(n=1\),此时无法进行任何操作,而且答案可能为负数。若 \(n\ge 2\) 则答案一定非负。

发现用 2、3 可以凑出来大于 1 的所有数,进一步发现经过一通操作之后,被删除的数肯定是原数列中若干段长度不小于 2 的连续区间,操作的类型和先后顺序是没有影响的,于是直接钦定删除的顺序是从左到右依次删的。

考虑一个简单 DP,令 \(a_{n + 1} = 0\),记 \(f_{i}(1\le i\le n+1)\) 表示钦定考虑到前 \(i\) 个数,最后一个没有被删除的数为 \(f_{i}\) 时的最大价值之和。初始化 \(f_{0} = 0\),考虑保留 \(a_ii\) 为最后没有被删除的数时,上一个数的位置,则有显然的转移:

\[f_{i}\leftarrow \begin{cases} f_{i - 1} + a_i\\ \max\limits_{0\le j\le i - 3}f_{j} + a_i\\ \max\limits_{0\le j\le i - 4}f_{j} + a_i\\ \end{cases}\]

答案即为 \(f_{n + 1}\)

观察转移方程可以发现第三种转移是没用的,已经被第二种包含了,套路地记前缀和 \(g_{i} = \max\limits_{0\le j\le i} f_{j}\) 优化上述转移即可,总时间复杂度 \(O(n)\) 级别。

import java.util.Scanner;

public class Main {
    public static Scanner sc;
    public static long[] arr;
    public static long[] f;
    public static long[] g;


    public static void solve() {
        int n = sc.nextInt();
        sc.nextLine();

        arr = new long[n + 2];
        for (int i = 1; i <= n; i++) arr[i] = sc.nextInt();
        arr[n + 1] = 0;
        sc.nextLine();

        if (n == 1) {
            System.out.println(arr[1]);
            return ;
        }

        f = new long[n + 2];
        g = new long[n + 2];
        for (int i = 0; i <= n; ++ i) f[i] = g[i] = 0;
        for (int i = 1; i <= n + 1; i++) {
            f[i] = f[i - 1] + arr[i];
            if (i >= 3) f[i] = Math.max(f[i], g[i - 3] + arr[i]);
            // if (i >= 4) f[i] = Math.max(f[i], g[i - 4] + arr[i]);
            g[i] = Math.max(g[i - 1], f[i]);
        }
        System.out.println(f[n + 1]);
    }

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

        int T = sc.nextInt();
        sc.nextLine();

        for (int t = 0; t < T; t++) {
            solve();
        }
    }
}

E

easy 构造。

先特判 \(n=1\) 时当且仅当 \(x=y\) 有解。

显然可以独立地构造每一个二进制位,考虑当前枚举到第 \(k\) 位。然后讨论 \(x, y\) 在这一位上的取值:

  • \(x\) 为 0,\(y\) 为 0:\(a_1\sim a_n\) 这一位只能是 0;
  • \(x\) 为 0,\(y\) 为 1:产生矛盾,无解;
  • \(x\) 为 1,\(y\) 为 0/1:\(a_i\sim a_n\) 中这一位上至少有一个 1,且 1 的数量为偶数/奇数。

仅需要考虑 \(x\) 某位上为 1 时具体如何构造即可。

为了保证构造的数均为正数,显然应当使 \(a_1\sim a_n\) 该位上尽量都不为 0,于是考虑先将 \(a_1\sim a_n\) 该位上置 1,若 \(y\) 不满足条件则再选择一个数,将该数的该位再置 0 即可。

容易发现只要满足上述选择的某一位置 0 的数,只要不都是同一个数就保证可以满足构造的 \(a_1\sim a_n\) 均为正数。

import java.util.Scanner;

public class Main {
    public static Scanner sc;
    public static int[] arr;


    public static void solve() {
        int n = sc.nextInt();
        int a = sc.nextInt();
        int b = sc.nextInt();
        sc.nextLine();

         if (n == 1) {
             System.out.println(a == b ? "YES\n" + a : "NO");
             return ;
         }

        boolean flag = true;
        arr = new int[n];
        for (int i = 0; i < n; i++) arr[i] = 0;
        for (int i = 0, p = 0; i < 31; i++) {
            int x = (a >> i) & 1;
            int y = (b >> i) & 1;

            if (x == 0 && y == 0) continue;
            if (x == 0 && y == 1) {
                flag = false;
                break;
            }

            for (int j = 0; j < n; ++ j) arr[j] += (1 << i);
            if ((y == 1 && n % 2 == 0) || (y == 0 && n % 2 == 1)) {
                arr[(++ p) % n] -= (1 << i);
            }
        }

        for (int i = 0; i < n; ++ i) {
            if (arr[i] == 0) {
                flag = false;
                break;
            }
        }
        if (!flag) {
            System.out.println("NO");
            return ;
        }

        System.out.println("YES");
        for (int i = 0; i < n; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }

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

        int T = sc.nextInt();
        sc.nextLine();

        for (int t = 0; t < T; t++) {
            solve();
        }
    }
}

F

本场最搞笑题。

一眼看上去是某种考虑枚举每个位置是什么字符,然后记录每种前缀各出现了多少次的 DP,然而受 ? 的影响改变了字符串形态,无法 DP 计数记出现次数的最大值。

那咋办啊?先考虑怎么求合法的子序列 ovo 数量。发现 ovo 这个形状左右对称而且长度只有 3 非常优美,进一步容易发现,对于某个已确定的字符串 \(s\),记 \(\operatorname{pre}_i, \operatorname{suf}_i\) 分别表示前缀 \([1, i], [i, n]\) 中有多少 o,则合法子序列 ovo 数量为:

\[\sum_{s_{i}=\text{v}} \operatorname{pre}_{i - 1}\times \operatorname{suf}_{i + 1} \]

观察这个式子,容易发现 o 出现位置越接近两侧可能的贡献越大,v 出现位置越接近中间可能的贡献越大。于是贪心地猜测被修改成 v? 一定是字符串一段中间的一段连续区间,区间两侧的所有 ? 都被修改成 o

发现 \(|s|\le 500\),考虑枚举被修改成 v? 的区间 \([l, r]\),然后大力构造修改后的字符串,按照上述式子对子序列计数并取最大值即可。

总时间复杂度 \(O(|s|^3)\) 级别。

import java.util.Scanner;

public class Main {
    public static Scanner sc;
    public static String s;


    public static void solve() {
        s = sc.nextLine();
        int n = s.length();
        s = "$" + s;

        long ans = 0;
        for (int l = 1; l <= n; ++ l) {
            for (int r = l; r <= n; ++ r) {
                long[] pre = new long[n + 2];
                long[] suf = new long[n + 2];
                long sum = 0;
                for (int i = 0; i <= n + 1; ++ i) pre[i] = suf[i] = 0;
                for (int i = 1; i <= n; ++ i) pre[i] = pre[i - 1] + ((s.charAt(i) == 'o' || ((i < l || i > r) && s.charAt(i) == '?')) ? 1 : 0);
                for (int i = n; i >= 0; -- i) suf[i] = suf[i + 1] + ((s.charAt(i) == 'o' || ((i < l || i > r) && s.charAt(i) == '?')) ? 1 : 0);
                for (int i = 1; i < l; ++ i) if (s.charAt(i) == 'v') sum += pre[i - 1] * suf[i + 1];
                for (int i = l; i <= r; ++ i) if (s.charAt(i) == 'v' || s.charAt(i) == '?') sum += pre[i - 1] * suf[i + 1];
                for (int i = r + 1; i <= n; ++ i) if (s.charAt(i) == 'v') sum += pre[i - 1] * suf[i + 1];
                ans = Math.max(ans, sum);
            }
        }
        System.out.println(ans);
    }

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

        int T = sc.nextInt();
        sc.nextLine();

        for (int t = 0; t < T; t++) {
            solve();    
        }
    }
}

写在最后

我是废物。

posted @ 2025-03-30 23:17  Luckyblock  阅读(64)  评论(0)    收藏  举报