牛客周赛 Round 31 解题报告 | 珂学家 | 设计 + 组合

小红小紫替换

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


前言

alt


整体评价

D题出的蛮好的,其实做过LruCache题的同学,基本都会,即Map+双向链表技巧。

E题典型的DP题,负数可以引入偏移来解决。

F题是道数学题,组合+乘法原理。


欢迎关注

珂朵莉 牛客周赛专栏

珂朵莉 牛客小白月赛专栏


A. 小红小紫替换

思路: 模拟

s = input()

if s == "kou":
    print ("yukari")
else:
    print (s)

B. 小红的因子数

思路: 质因子拆解

x = int(input())

def factors(n: int) -> int:
    res = 0
    i = 2
    while i <= n // i:
        if n % i == 0:
            res += 1
            while n % i == 0:
                n = n // i
        i += 1
    if n > 1:
        res += 1
    return res
    
print (factors(x))

C. 小红的字符串中值

思路: 枚举

对于i个字符,如果其为目标字符

则可提供的奇数子字符串数位:

min(i+1,n - i)

nn, t = input().split()
n = int(nn)
s = input()

res = 0
for i in range(n):
    if s[i] == t[0]:
        d = min(i, n - 1 - i) + 1
        res += d 

print (res)


D. 小红数组操作

思路: 复合数据结构

  • map

  • 双向链表

双向链表,方便数据的插入和删除, map方便数据的快速定位

很典的题

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

public class Main {

    static
    class Structure {
        static
        class Node {
            public Node left, right;
            int val;
            public Node() {}
            public Node(int val) {
                this.val = val;
            }
        }

        Map<Integer, Node> hash = new HashMap<>();
        Node head, tail;

        public Structure() {
            head = new Node();
            tail = new Node();
            head.right = tail;
            tail.left = head;
        }

        void add(int x, int y) {
            Node yn = hash.get(y);
            Node cur = new Node(x);
            Node second = yn.right;
            yn.right = cur;
            cur.left = yn;
            cur.right = second;
            second.left = cur;
            hash.put(x, cur);
        }

        void addHead(int x) {
            Node cur = new Node(x);
            Node second = head.right;
            head.right = cur;
            cur.left = head;
            cur.right = second;
            second.left = cur;
            hash.put(x, cur);
        }

        void remove(int x) {
            Node xn = hash.get(x);
            Node left = xn.left, right = xn.right;
            left.right = right;
            right.left = left;
            hash.remove(x);
        }

        List<Integer> toList() {
            List<Integer> res = new ArrayList<>();
            Node iter = head.right;
            while (iter != tail) {
                res.add(iter.val);
                iter = iter.right;
            }
            return res;
        }

    }

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

        Structure structure = new Structure();
        int n = sc.nextInt();
        for (int i = 0; i < n; i++) {
            int op = sc.nextInt();
            if (op == 1) {
                int x = sc.nextInt(), y = sc.nextInt();
                if (y == 0) {
                    structure.addHead(x);
                } else {
                    structure.add(x, y);
                }
            } else {
                int x = sc.nextInt();
                structure.remove(x);
            }
        }
        List<Integer> res = structure.toList();

        System.out.println(res.size());
        System.out.println(res.stream().map(String::valueOf).collect(Collectors.joining(  " ")));
    }

}

E. 小红的子集取反

思路: 动态规划

1. 线性DP

构建dp[i][j], i为前i项,j为构建和,其值为最小的取反次数

状态转移

dp[i][j] = min(dp[i - 1][j + arr[i]], dp[i - 1][j - arr[j]] + 1)

因为是线性,所以借助滚动数组进行优化

import java.io.BufferedInputStream;
import java.util.Arrays;
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[] arr = new int[n];
        for (int i = 0; i < n; i++) {
            arr[i] = sc.nextInt();
        }

        int inf = Integer.MAX_VALUE / 10;
        int offset = n * 200;
        int[] dp = new int[2 * offset + 1];
        Arrays.fill(dp, inf);

        dp[offset] = 0;

        for (int i = 0; i < n; i++) {
            int[] dp2 = new int[2 * offset + 1];
            Arrays.fill(dp2, inf);
            for (int j = 0; j <= 2 * offset; j++) {
                if (dp[j] == inf) continue;
                if (j + arr[i] <= 2 * offset && j + arr[i] >= 0) {
                    if (dp2[j + arr[i]] > dp[j]) {
                        dp2[j + arr[i]] = dp[j];
                    }
                }
                if (j - arr[i] >= 0 && j - arr[i] <= 2 * offset) {
                    if (dp2[j - arr[i]] > dp[j] + 1) {
                        dp2[j - arr[i]] = dp[j] + 1;
                    }
                }
            }
            dp = dp2;
        }

        if (dp[offset] == inf) {
            System.out.println(-1);
        } else {
            System.out.println(dp[offset]);
        }
    }

}

2. 0-1背包DP

除了这个思路外,也可以引入方程组

  • x + y = s

  • x - y = 0

推导 y = s / 2, s必然为偶数

问题就变为经典的0-1背包问题,从集合中挑选最小的元素,使得其为S/2.

import java.io.BufferedInputStream;
import java.util.Arrays;
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 sum = 0;
        int[] arr = new int[n];
        for (int i = 0; i < n; i++) {
            arr[i] = sc.nextInt();
            sum += arr[i];
        }

        if (sum % 2 == 1) {
            System.out.println(-1);
            return;
        }

        int inf = Integer.MAX_VALUE / 10;
        int offset = n * 200; // 值域范围
        int[] dp = new int[2 * offset + 1];
        Arrays.fill(dp, inf);

        dp[offset] = 0;
        for (int i = 0; i < n; i++) {
            int[] dp2 = dp.clone();
            // 逻辑变成取或不取
            for (int j = 0; j < dp.length; j++) {
                if (j + arr[i] >= 0 && j + arr[i] <= 2 * offset) {
                    if (dp2[j + arr[i]] > dp[j] + 1) {
                        dp2[j + arr[i]] = dp[j] + 1;
                    }
                }
            }
            dp = dp2;
        }

        if (dp[offset + sum/2] == inf) {
            System.out.println(-1);
        } else {
            System.out.println(dp[offset + sum/2]);
        }
    }

}

F. 小红的连续段

思路: 组合+乘法原理

对于t个段,其由n1 = t/2, n2 = (t+1)/2 两部分组成

根据隔板法

令a字母构建n1端,其组合数为 C(x - 1, n1 - 1)

令b字母构建n2端,其组合数为 C(y - 1, n2 - 1)

综合为 C(x-1, n1-1) * C(y-1, n2-1)

而a为n2段,b为n1段,依然成立

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

public class Main {

    static class Factorial {
        int n;
        long mod;
        long[] fac, inv;

        public Factorial(int n, long mod) {
            this.n = n;
            this.mod = mod;
            this.fac = new long[n + 1];
            this.inv = new long[n + 1];

            fac[0] = 1;
            for (int i = 1; i <= n; i++) {
                fac[i] = fac[i - 1] * i % mod;
            }
            // 费马小定律
            inv[n] = ksm(fac[n], mod - 2);
            for (int i = n - 1; i >= 0; i--) {
                inv[i] = inv[i + 1] * (i + 1) % mod;
            }
        }

        public long fac(int m) {
            return fac[m];
        }

        public long facInv(int m) {
            return inv[m];
        }

        public long inv(int m) {
            // 0没有逆元
            return inv[m] * fac[m - 1] % mod;
        }

        public long comb(int m, int k) {
            return fac[m] * inv[k] % mod * inv[m - k] % mod;
        }

        private long ksm(long b, long v) {
            long r = 1l;
            while (v > 0) {
                if (v % 2 == 1) {
                    r = r * b % mod;
                }
                b = b * b % mod;
                v /= 2;
            }
            return r;
        }
    }

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

        int n = x + y;
        long mod = (long)1e9 + 7;
        Factorial factorial = new Factorial(n, mod);

        List<Long> ans = new ArrayList<>();
        for (int i = 1; i <= n; i++) {
            long res = 0;
            int a = (i + 1) / 2;
            int b = i / 2;
            if (a <= x && a >= 1 && b <= y && b >= 1) {
                res += factorial.comb(x - 1, a - 1) * factorial.comb(y - 1, b - 1) % mod;
                res %= mod;
            }
            if (a <= y && a >= 1 && b <= x && b >= 1) {
                res += factorial.comb(y - 1, a - 1) * factorial.comb(x - 1, b - 1) % mod;
                res %= mod;
            }
            ans.add(res);
        }

        System.out.println(ans.stream().map(String::valueOf).collect(Collectors.joining("\n")));
    }

}

写在最后

alt

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

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

全部评论
请教个问题,为啥t个段一定是以平分的形式构建的呢?
2 回复 分享
发布于 2024-02-06 14:05 湖南
膜拜Orz
1 回复 分享
发布于 2024-02-05 21:53 浙江

相关推荐

不愿透露姓名的神秘牛友
07-10 13:54
点赞 评论 收藏
分享
06-27 12:54
已编辑
门头沟学院 Java
累了,讲讲我的大学经历吧,目前在家待业。我是一个二本院校软件工程专业。最开始选专业是觉得计算机感兴趣,所以选择了他。本人学习计算机是从大二暑假结束开始的,也就是大三开始。当时每天学习,我个人认为Java以及是我生活的一部分了,就这样持续学习了一年半,来到了大四上学期末,大概是在12月中旬,我终于找的到了一家上海中厂的实习,但我发现实习生的工作很枯燥,公司分配的活也不多,大多时间也是自己在自学。就这样我秋招末才找到实习。时间来到了3月中旬,公司说我可以转正,但是转正工资只有7000,不过很稳定,不加班,双休,因为要回学校参加答辩了,同时当时也是心高气傲,认为可以找到更好的,所以放弃了转正机会,回学校准备论文。准备论文期间就也没有投递简历。然后时间来到了5月中旬,这时春招基本也结束了,然后我开始投递简历,期间只是约到了几家下场面试。工资也只有6-7k,到现在我不知道该怎么办了。已经没有当初学习的心劲了,好累呀,但是又不知道该干什么去。在家就是打游戏,boss简历投一投。每天日重一次。26秋招都说是针对26届的人,25怎么办。我好绝望。要不要参加考公、考研、央国企这些的。有没有大佬可以帮帮我。为什么感觉别人找工作都是顺其自然的事情,我感觉自己每一步都在艰难追赶。八股文背了又忘背了又忘,我每次都花很长时间去理解他,可是现在感觉八股、项目都忘完了。真的已经没有力气再去学习了。图片是我的简历,有没有大哥可以指正一下,或者说我应该走哪条路,有点不想在找工作了。
码客明:太累了就休息一下兄弟,人生不会完蛋的
如果实习可以转正,你会不...
点赞 评论 收藏
分享
06-11 17:39
门头沟学院 Java
小呆呆的大鼻涕:卧槽,用户彻底怒了
点赞 评论 收藏
分享
评论
23
2
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务