大厂真题 / 得物

得物 4.26 笔试真题

本场考试概述

考试时间:2026年4月26日 考试岗位:暑期实习 难度评级:中等

考点分析

  1. 第一题:模拟 / 枚举(难度简单)
  2. 第二题:多重背包 DP(难度中等)
  3. 第三题:枚举 + 哈希(难度中等)

建议策略

  1. 第一题是签到题,枚举底数即可,注意范围分析
  2. 第二题是经典多重背包,掌握二进制拆分模板即可
  3. 第三题需要将直线规范化表示,考察计算几何基础

第一题:特殊立方数

题目描述

如果一个正整数 $M$ 满足:$M$ 是 $d$ 位数,且 $M^3$ 的最后 $d$ 位恰好等于 $M$,则称 $M$ 为特殊立方数

例如 $25^3 = 15625$,最后 2 位是 25,因此 25 是特殊立方数。

给定区间 $[a, b]$($b \leq 10^9$),统计其中有多少个特殊立方数。

样例

输入

1 200

输出

3

思路分析

第一步:枚举底数而非枚举立方数

直接枚举 $[a, b]$ 内的每个数判断是否为特殊立方数?范围太大($10^9$),不可行。

换个思路:既然特殊立方数 $K = M^3$,那我们枚举 $M$。由于 $K \leq 10^9$,所以 $M \leq 1000$。只需要枚举 1 到 1000,共 1000 个数。

第二步:逐个判断

对每个 $M$:计算 $K = M^3$,检查 $K$ 是否在 $[a, b]$ 范围内,再取 $K$ 的最后 $d$ 位($d$ 是 $M$ 的位数)判断是否等于 $M$。

题解代码

import sys
input = sys.stdin.readline

a, b = map(int, input().split())

cnt = 0
for M in range(1, 1001):
    K = M ** 3
    if K > b:
        break
    if K < a:
        continue
    mod = 10 ** len(str(M))
    if K % mod == M:
        cnt += 1

print(cnt)

复杂度分析

时间复杂度:$O(\sqrt[3]{b})$,最多枚举 1000 个底数。 空间复杂度:$O(1)$。


第二题:挖掘宝石

题目描述

$N$ 种宝石,第 $i$ 种有 $x_i$ 颗,每颗价值 $y_i$、挖掘耗时 $z_i$ 分钟。总游戏时间 $T$ 分钟,求在时间内能获得的最大价值。

$N \leq 100$,$T \leq 1000$,$x_i \leq 100$。

样例

输入

3 10
2 5 5
3 4 3
2 8 6

输出

12

思路分析

第一步:识别多重背包

每种宝石数量有限(最多 $x_i$ 颗),这是经典的多重背包问题。$N \leq 100$,$T \leq 1000$,直接对每种宝石枚举取几颗再做 DP,复杂度 $O(N \times T \times \max(x_i))$ 可行但偏大。

第二步:二进制拆分优化

将第 $i$ 种宝石的 $x_i$ 颗拆成 $1, 2, 4, \dots$ 和余数这几组,每组视为一个独立物品。例如 7 颗拆为 $1 + 2 + 4$,三组可以组合出 $0 \sim 7$ 的任意取法。拆分后转化为 01 背包,复杂度降到 $O(N \times T \times \log(\max(x_i)))$。

第三步:逆序遍历

01 背包的关键:从大到小更新容量,保证每个虚拟物品在一轮中至多被选一次。

题解代码

import sys
input = sys.stdin.readline

N, T = map(int, input().split())
gems = []
for _ in range(N):
    x, y, z = map(int, input().split())
    gems.append((x, y, z))

f = [0] * (T + 1)
for cnt, val, cost in gems:
    k = 1
    remain = cnt
    while remain > 0:
        take = min(k, remain)
        w = take * cost
        v = take * val
        for j in range(T, w - 1, -1):
            f[j] = max(f[j], f[j - w] + v)
        remain -= take
        k *= 2

print(f[T])

复杂度分析

时间复杂度:$O(N \times T \times \log(\max(x_i)))$,每种宝石拆出 $O(\log x_i)$ 个虚拟物品,每个遍历 $T$ 个容量。 空间复杂度:$O(T)$,一维滚动 DP 数组。


第三题:计算好直线的数量

题目描述

二维平面上有 $n$ 个坐标两两不同的点,如果一条直线经过至少 $k$ 个点,则称为”好直线”。求平面中好直线的条数。

$n \leq 500$,$k \geq 2$。

样例

输入

5 2
0 0
2 0
0 2
2 2
1 1

输出

6

思路分析

第一步:枚举点对

$n \leq 500$,枚举所有 $\binom{n}{2}$ 个点对的复杂度为 $O(n^2) \approx 1.25 \times 10^5$,完全可行。

第二步:直线规范化

两个不同点对可能确定同一条直线。将每条直线用规范化的整数三元组 $(a, b, c)$ 唯一表示:

两点 $(x_1, y_1), (x_2, y_2)$ 确定直线 $ax + by + c = 0$,其中 $a = y_2 - y_1$,$b = x_1 - x_2$,$c = x_2 y_1 - x_1 y_2$。

除以三者绝对值的 GCD 消除倍数差异,再规定首个非零系数为正,得到唯一标识。

第三步:收集点集并统计

用哈希表将规范化三元组映射到点下标集合。枚举所有点对后,统计集合大小 $\geq k$ 的直线条数。

题解代码

import sys
from math import gcd
input = sys.stdin.readline

n, k = map(int, input().split())
points = []
for _ in range(n):
    x, y = map(int, input().split())
    points.append((x, y))

def canonical(x1, y1, x2, y2):
    a = y2 - y1
    b = x1 - x2
    c = x2 * y1 - x1 * y2
    g = gcd(gcd(abs(a), abs(b)), abs(c))
    if g != 0:
        a //= g
        b //= g
        c //= g
    if a < 0 or (a == 0 and b < 0):
        a, b, c = -a, -b, -c
    return (a, b, c)

line_points = {}
for i in range(n):
    for j in range(i + 1, n):
        key = canonical(points[i][0], points[i][1], points[j][0], points[j][1])
        if key not in line_points:
            line_points[key] = set()
        line_points[key].add(i)
        line_points[key].add(j)

ans = sum(1 for pts in line_points.values() if len(pts) >= k)
print(ans)

复杂度分析

时间复杂度:$O(n^2)$,枚举所有点对,每对的规范化和集合插入均为 $O(1)$(均摊)。 空间复杂度:$O(n^2)$,最多 $O(n^2)$ 条不同直线。


小结

  • 第一题看似范围大($b \leq 10^9$),但枚举底数 $M$ 而非立方数,范围缩小到 1000,是典型的”换方向枚举”技巧
  • 第二题是多重背包模板题,二进制拆分将每种物品的 $x_i$ 颗拆成 $O(\log x_i)$ 组后转化为 01 背包
  • 第三题的难点在于直线的规范化表示——除以 GCD 并规定符号,使同一条直线始终映射到相同的三元组