大厂真题 / 拼多多

拼多多 4.12 笔试真题 - 暑期实习

本场考试概述

考试时间:2026年4月12日 考试岗位:通用 难度评级:中等

考点分析

  1. 第一题:差分数组(难度简单)
  2. 第二题:二分答案(难度中等)
  3. 第三题:贪心+前缀和+二分(难度中等)
  4. 第四题:树上贪心排序(难度中等)

建议策略

  1. 第一题差分数组是经典套路,先把基础分拿稳
  2. 第二题二分答案注意上取整写法避免死循环,第三题是本场最难题,区分两种折返方向是关键

第一题:赛车手赛道计时

题目描述

一个赛车场有 n 条平行赛道,每条赛道长度为 m 米。赛道上有 m 段区间,其中部分被水泥覆盖。第 i 段水泥覆盖了从第 l_i 条到第 r_i 条赛道。赛车在水泥地上的速度为 1 米/秒,在泥地上的速度为 2 米/秒。求最快到达终点的赛道编号(若有多条最快时选编号最小的),以及对应的最快时间。

样例

输入

3 2
1 2
2 3

输出

2 2

思路分析

第一步:建模耗时计算

每条赛道长度为 m 米,假设其中有 c 米是水泥地,则水泥段耗时 c * 1 秒,泥地段耗时 (m - c) * 2 秒,总耗时为 2m - c。因此水泥段数越多,耗时越短。

第二步:高效统计每条赛道的水泥覆盖量

每次水泥覆盖操作是对赛道区间 [l, r] 整体加 1,一共有 m 次操作。如果对每条赛道逐一累加,复杂度为 O(n * m),太慢。使用差分数组可以将每次区间加操作降为 O(1):在差分数组的 l 位置 +1,r+1 位置 -1,最后做一次前缀和即可还原每条赛道的水泥段数。

第三步:遍历取最优

前缀和扫描过程中,维护水泥段数最大值及其对应赛道编号。由于从小到大遍历,相同最大值时自然保留编号最小的赛道。

题解代码

import sys
input = sys.stdin.readline

def solve():
    n, m = map(int, input().split())
    # 差分数组,下标 1..n
    d = [0] * (n + 2)
    for _ in range(m):
        l, r = map(int, input().split())
        d[l] += 1
        d[r + 1] -= 1
    # 前缀和还原每条赛道的水泥段数
    best, best_id, cur = -1, 1, 0
    for i in range(1, n + 1):
        cur += d[i]
        if cur > best:
            best = cur
            best_id = i
    # 总耗时 = 2*m - 水泥段数
    print(2 * m - best, best_id)

solve()

复杂度分析

时间复杂度:O(n + m),差分数组构建 O(m),前缀和扫描 O(n)。 空间复杂度:O(n),差分数组大小。


第二题:最大化最小距离

题目描述

有 k 个投影站位,坐标各不相同。需要从中选出 n 个站位安排虚拟偶像,使得任意两名偶像之间的最小距离尽可能大。求这个最大化的最小距离。

样例

输入

1
3 2
1 6 8

输出

7

思路分析

第一步:识别二分答案的特征

题目要求”最大化最小距离”,这是经典的二分答案模型。如果最小距离为 d 时能放下 n 个偶像,那么 d-1 也一定能放下;反之如果 d 放不下,d+1 也放不下。答案具有单调性,可以二分。

第二步:设计 check 函数

对于给定的最小距离 d,将站位排序后从左到右贪心放置:第一个偶像放在最左站位,之后每次选下一个与上一个偶像距离不小于 d 的站位。如果能放下 n 个偶像则 d 可行。

第三步:二分边界与上取整

二分范围为 [0, max_coord - min_coord]。使用上取整写法 mid = lo + (hi - lo + 1) // 2,当 check 通过时收缩左边界 lo = mid,否则收缩右边界 hi = mid - 1,避免死循环。

题解代码

import sys
input = sys.stdin.readline

def check(x, n, d):
    """贪心检查:最小距离至少为 d 时能否放下 n 个偶像"""
    cnt, last = 1, x[0]
    for i in range(1, len(x)):
        if x[i] - last >= d:
            cnt += 1
            last = x[i]
            if cnt >= n:
                return True
    return cnt >= n

def solve(x, n):
    x.sort()
    lo, hi = 0, x[-1] - x[0]
    while lo < hi:
        # 上取整避免死循环
        mid = lo + (hi - lo + 1) // 2
        if check(x, n, mid):
            lo = mid
        else:
            hi = mid - 1
    return lo

T = int(input())
for _ in range(T):
    k, n = map(int, input().split())
    x = list(map(int, input().split()))
    print(solve(x, n))

复杂度分析

时间复杂度:O(T * k * log(max_coord)),每组数据排序 O(k log k),二分 O(log(max_coord)) 轮,每轮贪心 O(k)。 空间复杂度:O(k),存储站位坐标。


第三题:戴森环能源转运问题

题目描述

飞船从坐标 s 出发,沿一条直线收集能源仓。飞船总燃料为 N(每走 1 单位距离消耗 1 单位燃料),经过某个能源仓的位置时自动收集该仓的能源。共有 M 个能源仓,分布在直线上各个位置。求在燃料限制下,最多能收集多少能源。

样例

输入

1
4 3 5
8 4 6
4 7 2

输出

9

思路分析

第一步:分析最优路线结构

飞船从原点 s 出发,可以向左走也可以向右走。关键观察:最优策略一定是先往一个方向走到某个距离,然后折返往另一个方向走。不需要来回折返多次,因为每次折返都浪费双倍燃料。因此只有两种路线形态:先左后右,或先右后左。

第二步:将能源仓分为左右两侧

以起点 s 为界,将所有能源仓分成左侧(坐标 < s)和右侧(坐标 >= s)。每侧按照距离从近到远排序,因为走到距离 d 的仓会自动收集途中所有仓。构建距离数组和能源前缀和。

第三步:枚举一侧 + 二分另一侧

对于”先左后右”路线:左侧走了 l_cost 距离,消耗燃料 2 * l_cost(去程 + 折返),剩余燃料 N - 2 * l_cost 可用于右侧。对于”先右后左”路线:右侧走了 r_cost 距离,消耗 2 * r_cost,剩余用于左侧。枚举一侧取前 i 个仓,通过二分在另一侧的距离数组上找到剩余燃料最多能到达的位置,累加两侧的能源前缀和取最大值。

题解代码

import sys
input = sys.stdin.readline
from bisect import bisect_right

def solve():
    N, M, s = map(int, input().split())
    pos = list(map(int, input().split()))
    energy = list(map(int, input().split()))
    # 按距离分成左右两侧
    left_side, right_side = [], []
    for i in range(M):
        if pos[i] < s:
            left_side.append((s - pos[i], energy[i]))
        else:
            right_side.append((pos[i] - s, energy[i]))
    left_side.sort()
    right_side.sort()

    def build(side):
        """构建距离数组和能源前缀和"""
        dist, pre = [0], [0]
        for d, e in side:
            dist.append(d)
            pre.append(pre[-1] + e)
        return dist, pre

    l_dist, l_sum = build(left_side)
    r_dist, r_sum = build(right_side)
    ln, rn = len(left_side), len(right_side)

    ans = 0
    # 枚举左侧取前 i 个,二分右侧可达范围
    for i in range(ln + 1):
        l_cost = l_dist[i]
        # 先左后右:燃料消耗 2*l_cost + r_cost
        remain = N - 2 * l_cost
        if remain >= 0:
            j = bisect_right(r_dist, remain) - 1
            ans = max(ans, l_sum[i] + r_sum[j])
        # 先右后左:燃料消耗 l_cost + 2*r_cost
        remain = N - l_cost
        if remain >= 0:
            j = bisect_right(r_dist, remain // 2) - 1
            ans = max(ans, l_sum[i] + r_sum[j])

    # 枚举右侧取前 j 个,二分左侧可达范围
    for j in range(rn + 1):
        r_cost = r_dist[j]
        # 先右后左:燃料消耗 2*r_cost + l_cost
        remain = N - 2 * r_cost
        if remain >= 0:
            i = bisect_right(l_dist, remain) - 1
            ans = max(ans, l_sum[i] + r_sum[j])
    print(ans)

T = int(input())
for _ in range(T):
    solve()

复杂度分析

时间复杂度:O(T * M * log M),每组数据排序 O(M log M),枚举+二分 O(M log M)。 空间复杂度:O(M),存储分侧后的距离和前缀和数组。


第四题:魔法树能量水晶分配

题目描述

给定一棵 n 个节点的树,每个节点有一个共鸣频率。需要为每个节点分配水晶,规则如下:每个节点至少分配 1 颗水晶;若两个相邻节点的频率不同,则频率更高的节点必须严格拥有更多水晶。求满足条件的最少总水晶数。

样例

输入

4
1 3 2 4
0 1
1 2
2 3

输出

6

思路分析

第一步:理解约束关系

树上每条边连接两个节点,如果它们频率不同,高频节点的水晶数必须严格大于低频节点。频率相同的相邻节点之间没有约束。这类似于”糖果分配”问题在树上的推广。

第二步:贪心策略 – 从低频到高频处理

为了让总水晶数最少,应该让每个节点的水晶数尽可能小。将所有节点按频率从小到大排序,依次处理。频率最低的节点没有任何”必须比谁多”的约束(因为没有更低频的邻居),直接分配 1 颗。

第三步:逐步递推

处理某个节点 u 时,所有频率严格小于 u 的邻居已经确定了水晶数。u 必须比这些邻居都多,所以 u 的水晶数 = max(所有低频邻居的水晶数) + 1。如果 u 没有低频邻居,则分配 1 颗。频率相同的邻居之间互不影响,无需额外处理。排序保证了处理顺序的正确性。

题解代码

import sys
input = sys.stdin.readline

def solve():
    n = int(input())
    freq = list(map(int, input().split()))
    adj = [[] for _ in range(n)]
    for _ in range(n - 1):
        u, v = map(int, input().split())
        adj[u].append(v)
        adj[v].append(u)
    crystal = [1] * n
    # 按频率从小到大排序节点
    order = sorted(range(n), key=lambda x: freq[x])
    for u in order:
        for v in adj[u]:
            # 频率更低的邻居已确定,当前节点必须比它多
            if freq[v] < freq[u]:
                crystal[u] = max(crystal[u], crystal[v] + 1)
    print(sum(crystal))

solve()

复杂度分析

时间复杂度:O(n log n),排序 O(n log n),遍历所有边 O(n)。 空间复杂度:O(n),邻接表和水晶数组。


小结

  • 第一题差分数组是区间操作的基本功,核心在于将 m 次区间加 1 转化为差分操作再前缀和还原
  • 第二题二分答案是”最大化最小值”的标准模型,check 函数用贪心从左到右放置,注意上取整写法防止死循环
  • 第三题是本场最难题,关键洞察是最优路线只有”先左后右”或”先右后左”两种形态,然后用前缀和+二分高效枚举所有组合
  • 第四题树上水晶分配本质是拓扑排序思想的变体,按频率从小到大处理保证了依赖关系的正确性