牛客小白月赛77 解题报告 | 珂学家 | 反悔堆 + 字符串hash + 二进制递推


前言

alt


整体评价

虽然E题测试数据有误,导致比赛期间非c++语言输入异常,但是瑕不掩瑜。题目质量还是蛮高的,D题设计卡单Hash,还是很用心的,E题这个反悔堆出的也很精彩,F题的官解做法也让人眼前一亮。


A. 小Why的方阵

由于对称性,可以假定改动的值在左上角

x + a[0][1] = a[1][0] + a[1][1]

x + a[1][0] = a[0][1] + a[1][1]

恒成立的条件为 a[0][1] == a[1][0]

因此猜测,只要存在一个对角线元素相等,则必然可以构造解

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 a00 = sc.nextInt(), a01 = sc.nextInt();
        int a10 = sc.nextInt(), a11 = sc.nextInt();
        if (a00 == a11 || a01 == a10) {
            System.out.println("YES");
        } else {
            System.out.println("NO");
        }
    }

}


B. 小Why的循环置换

很经典的一个知识点,置换环,它由多个互相独立的环构成.

对于某个成员数k的环,其最多需要k-1次,才能调整为自然序

这样的话,m个环,即需要

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 m = sc.nextInt();
        System.out.println(n - m);
    }

}

C. 小Why的商品归位

这是一道非常有趣的题

如果数据量小的话,感觉可以模拟,按 目标库编号 小的优先放购物车。

这种一入一出,是不是特别像 差分的做法

因此我们可以猜测,轮次和差分的最大数有关

不过这边取出购物车在先,从柜台取出在后,因此右端点不需要+1, 这是唯一和差分有区别的地方

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 m = sc.nextInt();
        int k = sc.nextInt();

        int[] diff = new int[n + 2];
        for (int i = 0; i < m; i++) {
            int from = sc.nextInt(), to = sc.nextInt();
            diff[from]++;
            diff[to]--; // 不需要+1
        }

        int ans = 0;
        int acc = 0;
        for (int i = 1; i <= n; i++) {
            acc += diff[i];
            ans = Math.max(ans, (acc + k - 1) / k);
        }
        System.out.println(ans);
    }

}

D. 小Why的密码锁

这题第一感觉就是字符串hash

按照固定长度m,构建一个Map<字符串hash值,个数>

这题难点在于

  • 子串不能重叠
  • 数量恰好为k个

因此单纯地保存个数,可能不够,需要保存对应的区间,Map<字符串hash值,list<int[]>>

那保存区间列表后,又该如何处理呢?

可以根据贪心,挑选最多的不重叠个区间,如果恰好为k,那就是可能的密码串

不过这题卡单hash,反正试了13,17都不行,还好有预案,就是试双hash

赛后有群友反馈,是针对mod=1000000007,998244353设计测试数据了

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(), k = sc.nextInt();
        char[] str = sc.next().toCharArray();

        Map<Long, List<int[]>> hash = new HashMap<>();

        long base = 1l;
        long acc = 0;
        int p = 13;


        long base1 = 1l;
        long acc1 = 0;
        int p1 = 1771;

        long mod = 10_0000_0007l;
        for (int i = 0; i < m - 1; i++) {
            acc = (acc * p % mod + (str[i] - '0')) % mod;
            base = base * p % mod;

            acc1 = (acc1 * p1 % mod + (str[i] - '0')) % mod;
            base1 = base1 * p1 % mod;
        }

        for (int i = m - 1; i < n; i++) {
            acc = (acc * p % mod + (str[i] - '0')) % mod;
            acc1 = (acc1 * p1 % mod + (str[i] - '0')) % mod;

            hash.computeIfAbsent((acc1 << 32)|acc, x -> new ArrayList<>())
              .add(new int[] {i - m + 1, i});

            acc = (acc - base * (str[i - (m - 1)] - '0') % mod) % mod;
            acc = (acc + mod) % mod;

            acc1 = (acc1 - base1 * (str[i - (m - 1)] - '0') % mod) % mod;
            acc1 = (acc1 + mod) % mod;
        }

        int ans = 0;
        for (Map.Entry<Long, List<int[]>> kv: hash.entrySet()) {
            List<int[]> list = kv.getValue();
            if (list.size() >= k) {
                int last = -1;
                int nk = 0;
                for (int[] v: list) {
                    if (v[0] > last) {
                        last = v[1];
                        nk++;
                    }
                }
                if (nk == k) {
                    ans++;
                }
            }
        }
        System.out.println(ans);

    }

}

E. 小Why的简单加减

这题负数必须和前面的数配对处理,才能变成非负数,这是大前提。

同时有个最多k次操作的限制,求数组中最多的非负数个数

对于某个负数要变成非负数(0),需要分类讨论

  • 前排都已经变成非负数,

    |arr[i]| <= min(前缀和, k + 负数的前缀和)

  • 前排至少有一个负数没有变非负数

    |arr[i]| <= k + 选中的负数前缀和

如果两者条件都不能满足了,则需要考虑,是否可以置换前排 已被非负数的 负数,看谁代价更小

这个置换过程,就是反悔过程,而维护替换的数据结构就是堆(最大堆)

因此这题,思路其实非常的明确,就是反悔贪心

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();
        long k = sc.nextLong();
        int nk = 0;
        long[] arr = new long[n];
        for (int i = 0; i < n; i++) {
            arr[i] = sc.nextLong();
            if (arr[i] >= 0) nk++;
        }
        // 反悔堆
        PriorityQueue<Long> pp = new PriorityQueue<>(Comparator.comparing(x -> -x));

        long acc = 0;
        long pacc = 0;
        int minus = 0;

        for (int i = 0; i < n; i++) {
            if (arr[i] >= 0) {
                acc += arr[i];
            } else {
                minus++;
                long tv = -1l * arr[i];
                if ((pacc + tv <= k) && (pacc + tv <= acc || pp.size() + 1 < minus)) {
                    pp.offer(tv);
                    pacc += tv;
                } else {
                    if (!pp.isEmpty() && pp.peek() > tv) {
                        pacc -= pp.poll();
                        pp.offer(tv);
                        pacc += tv;
                    }
                }
            }
        }
        System.out.println(nk + pp.size());
    }

}

F. 小Why的数论测试

对于这类从某一个二进制变成另一个二进制的问题

最好的方式还是递推

如果仅仅考虑

  • 加1操作
  • 乘2操作

那这个最小代价,其实可以立马求解出来。

关键是sqrt操作操作

可以递推从a到sqrt(b),状态数最多10^6, 可控,同时大于sqrt(b)不存在平方操作,可以保证最优解。

期间可以+1,x2,平方迁移,然后评估其非平方的总代价

这样时间复杂度为 sqrt(b)

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

public class Main {

    static long evaluate(long a, long b) {
        if (a > b) return Integer.MAX_VALUE;

        int n1 = 64 - Long.numberOfLeadingZeros(a);
        int n2 = 64 - Long.numberOfLeadingZeros(b);

        int dn = n2 - n1;
        long b1 = b >>> dn;

        if (a <= b1) {
            return (b1 - a) + dn + Long.bitCount(b - (b1 << dn));
        } else {
            return ((b1 << 1) - a) + (dn - 1) + Long.bitCount(b - (b1 << dn));
        }
    }

    public static void main(String[] args) {

        Scanner sc = new Scanner(new BufferedInputStream(System.in));

        int kase = sc.nextInt();
        while (kase-- > 0) {
            long a = sc.nextLong();
            long b = sc.nextLong();

            long ans = evaluate(a, b);

            if (a <= b / a) {
                int mz = (int) Math.sqrt(b);
                long[] vis = new long[mz + 1];
                Arrays.fill(vis, Integer.MAX_VALUE);

                int ia = (int) a;
                vis[ia] = 0;
                for (int i = ia; i <= mz; i++) {
                    ans = Math.min(ans, vis[i] + evaluate(i, b));

                    if (i + 1 <= mz) {
                        vis[i + 1] = Math.min(vis[i + 1], vis[i] + 1);
                    }
                    if (i * 2 <= mz) {
                        vis[i * 2] = Math.min(vis[i * 2], vis[i] + 1);
                    }

                    if ((long) i * i <= mz) {
                        vis[i * i] = Math.min(vis[i * i], vis[i] + 1);
                    } else {
                        // 这边需要保护
                        ans = Math.min(ans, vis[i] + 1 + evaluate((long) i * i, b));
                    }
                }
            }
            System.out.println(ans);
        }

    }

}

写在最后

alt

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

牛客小白月赛解题报告系列,适合算法入门,也适合求职算法

全部评论
小姐姐,你真棒
1 回复 分享
发布于 2023-09-05 11:41 上海

相关推荐

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