大厂真题 / 网易

网易雷火 4.26 笔试真题

本场考试概述

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

考点分析

  1. 第一题:数学公式 + 乘法逆元(难度中等)
  2. 第二题:FIFO 缓存模拟(难度中等)
  3. 第三题:三进制状压 DP(难度困难)
  4. 第四题:四层结构模拟(难度中等)

建议策略

  1. 第一题和第二题是稳拿分的数学/模拟题,优先保证正确
  2. 第三题是区分度题,关键在于识别出传闻数 $w \leq 10$ 可以状压
  3. 第四题代码量大但逻辑清晰,按层建模逐条模拟即可

第一题:喵居

题目描述

供台上有 $n$ 团「猫灵元魂」,第 $i$ 团灵力值为 $a_i$。每次融合操作选择两团元魂(灵力值分别为 $x, y$),消耗 $x \times y$,融合后生成灵力值为 $x + y$ 的新元魂。将所有元魂融合为 1 个,求最小总消耗对 $10^9 + 7$ 取模的结果。

多组测试数据,$n$ 之和不超过 $10^5$。

样例

输入

2
3
1 2 3
4
2 2 2 2

输出

11
24

思路分析

第一步:发现总消耗与顺序无关

每次融合 $x, y$ 花费 $x \times y$,全局”元素平方和”增加了 $2xy$(因为 $(x+y)^2 = x^2 + y^2 + 2xy$)。初始平方和为 $\sum a_i^2$,最终只剩一个值为 $S = \sum a_i$ 的元素,平方和变为 $S^2$。

总花费恰好是平方和的增量除以 2:

\[\text{cost} = \frac{S^2 - \sum a_i^2}{2}\]

与融合顺序完全无关。

第二步:取模计算

在模 $10^9 + 7$ 下,除以 2 等价于乘以 2 的模逆元。$10^9 + 7$ 是奇数,所以 $2^{-1} \equiv \frac{10^9 + 8}{2} = 500000004 \pmod{10^9 + 7}$。

题解代码

import sys
input = sys.stdin.readline

MOD = 10 ** 9 + 7
INV2 = (MOD + 1) // 2

def solve(a):
    s = 0
    sq = 0
    for x in a:
        xm = x % MOD
        s = (s + xm) % MOD
        sq = (sq + xm * xm) % MOD
    return (s * s % MOD - sq + MOD) % MOD * INV2 % MOD

T = int(input())
results = []
for _ in range(T):
    n = int(input())
    a = list(map(int, input().split()))
    results.append(str(solve(a)))
print('\n'.join(results))

复杂度分析

时间复杂度:$O(n)$,每个测试点遍历一次数组。 空间复杂度:$O(n)$,存储数组。


第二题:界面缓存

题目描述

MMORPG 游戏中的界面分为可缓存界面(关闭后暂存到缓存区,可快速恢复)和不可缓存界面(关闭后直接卸载)。缓存区容量为 $k$,采用 FIFO 淘汰策略。

给定 $n$ 种界面的类型和 $m$ 次操作(open x / close x),输出操作完毕后打开的界面列表和缓存区中的界面列表。

打开界面:若在缓存区中则恢复;若已打开则移到末尾;否则新建。 关闭界面:可缓存的进缓存区(满则 FIFO 淘汰);不可缓存的直接卸载。

样例

输入

4 9 2
1 1 0 1
open 1
open 2
open 3
close 2
close 3
close 1
open 4
open 1
open 4

输出

1 4
2

思路分析

第一步:理清两个队列

系统维护两个有序队列:打开列表记录当前正在显示的界面,缓存区暂存最近被关闭的可缓存界面。所有操作都是对这两个队列做增删移动。

第二步:分情况处理

  • open x:(1) 已打开 → 删除再追加到末尾;(2) 在缓存区 → 从缓存移出放入打开列表;(3) 都不在 → 新建并打开
  • close x:不在打开列表则忽略。可缓存的放入缓存区(FIFO 淘汰队头),不可缓存的直接丢弃

第三步:数据结构选择

OrderedDict 同时支持 $O(1)$ 的按键查找、删除和有序遍历,非常适合维护这两个队列。

题解代码

import sys
from collections import OrderedDict
input = sys.stdin.readline

n, m, k = map(int, input().split())
t = list(map(int, input().split()))
cacheable = [False] + [ti == 1 for ti in t]

opened = OrderedDict()
cache = OrderedDict()

for _ in range(m):
    parts = input().split()
    op, x = parts[0], int(parts[1])
    if op == "open":
        if x in opened:
            del opened[x]
            opened[x] = True
        elif x in cache:
            del cache[x]
            opened[x] = True
        else:
            opened[x] = True
    else:
        if x not in opened:
            continue
        del opened[x]
        if cacheable[x]:
            if len(cache) >= k:
                cache.popitem(last=False)
            cache[x] = True

out = []
out.append(' '.join(str(x) for x in opened) if opened else 'EMPTY')
out.append(' '.join(str(x) for x in cache) if cache else 'EMPTY')
print('\n'.join(out))

复杂度分析

时间复杂度:$O(m)$,每次操作只做常数次查找、删除、插入。 空间复杂度:$O(n)$,最多同时存储 $n$ 个界面。


第三题:传闻以此得解

题目描述

$n$ 个胜地可选,游历第 $i$ 个胜地消耗 $a_i$ 交子,每个胜地会给若干传闻各提供 1 条线索。一个传闻攒够 $\geq 2$ 条线索才算解开。每个胜地最多游历一次,总花费不超过预算 $m$。求最多能解开多少个传闻。

$n \leq 100$,$m \leq 10^9$,传闻数 $w \leq 10$。

样例

输入

3
4 5 3
2 2
1 2
3 2
1 3
2 1
2
4 2
2 3
3 3 2
2 1
1
2 1
2
3 2
1 2 2 3
2 2
1 2
3

输出

1
0
0

思路分析

第一步:为什么不能直接背包?

经典背包把”花了多少钱”当状态,但预算 $m$ 可达 $10^9$,开不了这么大的数组。

第二步:换状态维度——三进制状压

传闻总共才 $w \leq 10$ 个。每个传闻的线索数只关心三种情况:0 条、1 条、$\geq 2$ 条。把所有传闻的线索情况打包成一个三进制数,总状态数 $3^{10} = 59049$,完全可以枚举。

第三步:DP 定义与转移

定义 $f[s]$ = 让所有传闻的线索情况变成状态 $s$ 所需的最小花费。初始 $f[0] = 0$,其余为 $+\infty$。

对每个胜地(0/1 背包,逆序枚举),如果选了它,就把对应传闻位的三进制数字各加 1(已是 2 则不加)。最终遍历所有 $f[s] \leq m$ 的状态,数有几位等于 2,取最大值。

题解代码

import sys
input = sys.stdin.readline

def solve():
    n, m, w = map(int, input().split())
    sites = []
    for _ in range(n):
        parts = list(map(int, input().split()))
        a, k = parts[0], parts[1]
        clues = list(map(int, input().split()))
        mask = 0
        for c in clues:
            mask |= 1 << (c - 1)
        sites.append((a, mask))

    pw3 = [1] * (w + 1)
    for i in range(1, w + 1):
        pw3[i] = pw3[i - 1] * 3
    total = pw3[w]

    INF = float('inf')
    f = [INF] * total
    f[0] = 0

    for cost, mask in sites:
        for s in range(total - 1, -1, -1):
            if f[s] < INF and f[s] + cost <= m:
                ns = s
                tmp = mask
                while tmp:
                    j = (tmp & -tmp).bit_length() - 1
                    digit = (ns // pw3[j]) % 3
                    if digit < 2:
                        ns += pw3[j]
                    tmp &= tmp - 1
                val = f[s] + cost
                if val < f[ns]:
                    f[ns] = val

    best = 0
    for s in range(total):
        if f[s] <= m:
            cnt = 0
            tmp = s
            for _ in range(w):
                if tmp % 3 == 2:
                    cnt += 1
                tmp //= 3
            if cnt > best:
                best = cnt
    return best

T = int(input())
results = []
for _ in range(T):
    results.append(str(solve()))
print('\n'.join(results))

复杂度分析

时间复杂度:$O(n \times 3^w \times w)$,每个胜地遍历所有状态并更新三进制位。 空间复杂度:$O(3^w)$,DP 数组。


第四题:红点系统

题目描述

实现一个四层红点系统:

  • 红点类型(最底层):有”显示/隐藏”两种状态
  • 红点类型集合:包含若干红点类型,任意一个显示则集合显示
  • 按钮:绑定若干红点类型/集合,任意一个显示则按钮亮
  • 界面:绑定若干按钮,任意一个亮则界面显示红点

支持 6 种操作:Show/Hide 修改红点状态,Ask/AskUI 查询按钮/界面状态,Remove/Bind 修改按钮绑定。状态改变后,若界面从”亮”变”灭”则输出界面名。

样例

输入

3 1 3 2
Red1 1
Red2 0
Red3 1
RedGP1 2 Red1 Red2
Btn1 1 Red1
Btn2 1 RedGP1 Red3
Btn3 2 RedGP1 Red3
Panel1 1 Btn1
Panel2 2 Btn2 Btn3
6
Ask Btn2
Hide Red3
Hide Red1
Ask Btn3
AskUI Panel1
Show Red2

输出

YES
Panel1
Panel2
NO
NO

思路分析

第一步:理清四层结构

每一层的判定逻辑相同:任意一个子项亮,父项就亮。从底向上逐层查询即可。

第二步:分操作处理

  • Show/Hide:修改红点类型状态,改后检查所有界面是否从亮变灭
  • Ask/AskUI:纯查询,沿四层自底向上判定
  • Remove/Bind:修改按钮绑定关系,改后同样检查界面变化

第三步:界面状态变化检测

每次执行修改操作前,记录所有界面当前状态。操作后逐个比对,从亮变灭的按输入顺序输出。

数据规模小($n, m, x, y$ 均不大),暴力逐层查询完全够用。

题解代码

import sys
input = sys.stdin.readline

N, M, X, Y = map(int, input().split())

red_state = {}
for _ in range(N):
    parts = input().split()
    red_state[parts[0]] = parts[1] == '1'

group_members = {}
for _ in range(M):
    parts = input().split()
    name, num = parts[0], int(parts[1])
    group_members[name] = parts[2:2 + num]

btn_order = []
btn_binds = {}
for _ in range(X):
    parts = input().split()
    name, num = parts[0], int(parts[1])
    btn_order.append(name)
    btn_binds[name] = set(parts[2:2 + num])

panel_order = []
panel_btns = {}
for _ in range(Y):
    parts = input().split()
    name, num = parts[0], int(parts[1])
    panel_order.append(name)
    panel_btns[name] = parts[2:2 + num]

def is_active(name):
    if name in red_state:
        return red_state[name]
    if name in group_members:
        return any(red_state.get(r, False) for r in group_members[name])
    return False

def btn_active(btn):
    return any(is_active(b) for b in btn_binds.get(btn, set()))

def panel_active(panel):
    return any(btn_active(b) for b in panel_btns.get(panel, []))

S = int(input())
results = []
for _ in range(S):
    parts = input().split()
    op = parts[0]
    if op == "Ask":
        results.append("YES" if btn_active(parts[1]) else "NO")
    elif op == "AskUI":
        results.append("YES" if panel_active(parts[1]) else "NO")
    else:
        before = {p: panel_active(p) for p in panel_order}
        if op == "Show":
            if parts[1] in red_state:
                red_state[parts[1]] = True
        elif op == "Hide":
            if parts[1] in red_state:
                red_state[parts[1]] = False
        elif op == "Remove":
            btn, target = parts[1], parts[2]
            if btn in btn_binds:
                btn_binds[btn].discard(target)
        elif op == "Bind":
            btn, target = parts[1], parts[2]
            if btn in btn_binds and target not in btn_binds[btn]:
                btn_binds[btn].add(target)
        for p in panel_order:
            if before[p] and not panel_active(p):
                results.append(p)

print('\n'.join(results))

复杂度分析

时间复杂度:$O(S \times Y \times X \times M)$,每次操作最坏遍历所有界面、按钮、绑定项判断状态。规模很小,暴力够用。 空间复杂度:$O(N + M + X + Y)$,哈希表存储各实体及绑定关系。


小结

  • 第一题的核心发现是总消耗与融合顺序无关——利用平方和的变化推导出闭式公式 $\frac{S^2 - \sum a_i^2}{2}$,取模时用乘法逆元处理除法
  • 第二题是纯模拟题,OrderedDict 同时满足有序遍历和 $O(1)$ 查找删除,是维护 FIFO 队列的理想选择
  • 第三题的关键洞察是传闻数 $w \leq 10$,每个传闻只需区分 0/1/$\geq$2 三种状态,三进制状压后状态空间仅 $3^{10} \approx 6 \times 10^4$
  • 第四题无算法难度,考验工程能力——理清四层结构的”任一子项亮则父项亮”逻辑,逐操作模拟即可