大厂真题 / mihoyo

米哈游 2026-9-6 笔试真题 - 通用岗

证据边界:本文依据公开题解材料整理。原文中部分数学公式在网页抽取时以 SVG 形式保存,文本版无法可靠恢复的变量、约束或表达式不擅自补写;题面与答案应以实际考试页面为准。

本场考试概述

考试时间 :9 月 6 日 考试岗位 :通用 难度评级 :中等偏难 考点分析

  • • 第一题:构造 + 奇偶不变量分类讨论(难度中等)
  • • 第二题:线段树区间合并 + 三进制位权推导(难度困难)

建议策略

  • • 第一题的代码只有十几行,全部分数压在分类讨论是否穷尽上。先把总量守恒、最少步数、多余步数怎么消化这三层理清,再单独把 n=1 和 s=0 两个退化情形挡在最前面,漏掉任何一条都会挂在小数据上。
  • • 第二题不要一上来就写线段树。先把”互换 0/2 再翻转”这个复合变换展开成每一位的固定权重,确认它与区间长度无关、可以逐元素预处理,合并函数才写得出来;式子推不出来时,线段树写得再熟也无从下手。

第 1 题:道具分配

题目描述

一次整理操作是指:选择一个当前非空的格子,从中取出一件道具,放进另一个与之不同的格子里。道具既不会凭空出现,也不会凭空消失。 现在给定每个格子期望的道具件数 ,请判断能否恰好经过 次整理操作,使得第 个格子中恰好有 件道具。注意操作次数必须恰好是 次,既不能少也不能多。

输入描述

第一行输入三个整数 ,分别表示背包的格子数、初始道具件数和必须恰好完成的整理次数。 第二行输入 个整数 ,表示每个格子期望的道具件数。

输出描述

如果恰好 次整理操作后能够达成目标,输出 Yes,否则输出 No

样例1

输入

4 6 4
2 1 3 0

输出

Yes

样例解释 件道具全在 号格子,目标要求 号格子留下 件,另外 件分别送往 号格子 件、 号格子 件。每件道具各搬一次,一共 次操作,恰好等于 。

样例2

输入

3 4 1
1 2 1

输出

No

样例解释 号格子要从 件减到 件,必须有 件道具离开它,因此至少需要 次操作。 只有 ,无论怎样安排都达不到目标。

样例3

输入

2 2 4
2 0

输出

Yes

样例解释 目标与初始状态完全一致,最少需要 次操作。多出来的 次可以这样消耗:把一件道具搬到 号格子再搬回 号格子,如此往返两轮,共 次操作后道具仍然全部留在 号格子。

题解:分类讨论(构造 + 奇偶不变量)

题目问题拆解

道具只会被搬来搬去、不会增减,问能否恰好用 次操作把”全部堆在 号格子”变成给定的目标局面 。 这是一道判定条件全压在分类讨论里的构造题:最少要搬几次一眼可得,难点在于多出来的次数能不能被空转消化掉,而答案随格子数不同而不同。

算法实现

先看总量。一次操作只是把一件道具从一格挪到另一格,总数守恒, 时直接输出 No。 再看下界。 号格子起初有 件、目标留 件,每件要离开它的道具至少搬一次,而搬一次就足以直送终点,故最少操作次数为 一定做不到。剩下的问题只有一个:多出来的 次怎么消化。 只有两个格子时消化不掉奇数次:每次操作都发生在 号与 号之间, 号格子的件数每步恰好变化 ,奇偶被锁死,故要求 且 为偶数。 格子数到 以上,奇偶不再是障碍。把某件本来直送目标格的道具改成先绕第三个格子中转,代价只多 次,奇偶随之翻转。这条构造需要存在一件待搬的道具,即 ,此时任意 都可行。 是唯一的例外。目标就是初始局面,只能靠”搬出去再搬回来”空转,一来一回恰好 次,因此 或 才行, 必然停在被打破的局面上。 两处退化情形要挡在所有分支之前: 时没有第二个格子可去, 时没有道具可搬,都是一次操作也做不了,只有 成立。 尤其容易漏,它的 同样是 ,落进上一条规则就会把 误判成可行。 上限 , 与 在 C++、Java、Go 里一律用 位整数。

时空复杂度分析

时间复杂度 :。瓶颈在读入 个目标件数并求和,判定本身只有常数次比较,而读入这一趟省不掉。 空间复杂度 :,存下目标数组;若边读边累加,可压到 。 Python

## 道具分配 - 分类讨论(构造 + 奇偶不变量)


def can_finish(n, s, t, a):
    """判断能否恰好用 t 次操作把初始局面变成目标局面 a。"""
    # 操作只是搬运,道具总数守恒,所以总数对不上直接否定
    if sum(a) != s:
        return False
    # 没有道具可搬,或只有一个格子无处可搬,一步都动不了
    if s == 0 or n == 1:
        return t == 0
    # need 是必须离开 1 号格子的道具数,也就是达成目标所需的最少操作次数
    need = s - a[0]
    if n == 2:
        # 只有两格时每次操作都在两格之间来回,1 号格子的件数每步变化 1,
        # 奇偶性被锁死,因此总步数必须与 need 同奇偶
        return t >= need and (t - need) % 2 == 0
    if need >= 1:
        # 三格以上时可以让某件要搬走的道具绕第三个格子多走一步,
        # 于是奇偶不再是障碍,只要步数够用即可
        return t >= need
    # need == 0 说明目标就是初始局面:一步必然打破局面回不来,两步可以来回一趟
    return t == 0 or t >= 2


## 读入规模与目标局面,t 最大到 1e18,Python 整数天然不会溢出
n, s, t = map(int, input().split())
a = list(map(int, input().split()))
print("Yes" if can_finish(n, s, t, a) else "No")

第 2 题:三进制翻转查询

题目描述

把一个整数写成三进制表示时不含前导零,特别地,整数 写成一位的 。将一段连续符文上的整数各自写成三进制,再按下标从小到大依次拼接,就得到一个由 组成的数字串。 对这个数字串做一次解读的过程是:先把串中所有的 改成 、所有的 改成 ,数字 保持不变;再把整个串首尾翻转;最后把翻转后的串当作一个三进制数,求出它所表示的数值。这个数值可能非常大,输出时对 取模。

输入描述

第一行输入两个整数 ,表示符文个数和操作次数。 第二行输入 个整数 ,表示每个符文上的初始整数。 接下来 行,每行输入一个操作。若该行为 ,表示一次解读询问;若该行为 ,表示把第 个符文上的整数改成 。

输出描述

对每个询问操作输出一行一个整数,表示该段符文解读结果对 取模后的值。

样例1

输入

4 3
4 2 0 5
1 1 4
2 2 7
1 2 3

输出

139
21

样例解释 的三进制分别是 ,拼接得到 。互换 与 后得到 ,翻转后得到 ,作为三进制数其值为 。 随后第 个符文被改成 ,第 到第 个符文的三进制是 和 ,拼接得到 。互换后是 ,翻转后是 ,作为三进制数其值为 。

样例2

输入

3 2
0 1 2
1 1 3
1 2 2

输出

5
1

样例解释 各写成一位三进制,拼接得到 。互换后是 ,翻转后是 ,作为三进制数其值为 。 只询问第 个符文时数字串是 ,互换与翻转都不改变它,其值为 。

样例3

输入

2 3
8 1
1 1 2
2 1 0
1 1 2

输出

9
5

样例解释 与 的三进制是 和 ,拼接得到 。互换后是 ,翻转后是 ,作为三进制数其值为 。 把第 个符文改成 之后,数字串变成 ,互换后是 ,翻转后是 ,作为三进制数其值为 。

题解:线段树(区间合并)+ 三进制位权闭式

题目问题拆解

一排 个数各自写成三进制后拼成一个数字串,每次询问取出一段做” 与 互换、整串翻转”再按三进制求值,其间还夹着单点修改。 这是一道把复合变换化简成可结合运算的数据结构题:难点不在线段树,而在翻转看似整段级别的操作,须先证明它能拆成每个元素各自的贡献。

算法实现

先看朴素做法。每次询问把区间内的数逐个转成三进制、拼串、互换、翻转、求值,单次 , 次在 均取 时是 量级的字符操作,超时。 卡点在翻转。它把整串首尾对调,看上去每一位的权重都取决于整段有多长。展开就会发现并非如此:设区间拼出的串从左到右是 ,互换 与 相当于每位取 ,翻转后原本的第 位落到从右数第 位、权重恰为 ,于是 两步合起来只是一句话:左边第 位的权重是 ,与整段长度无关。 于是每个数都能脱离上下文单独编码。把 的三进制位数记作 、从左数第 位记作 ,则 两段拼接时右段的每一位都被左段整体推高了 位,因此合并规则为 这个运算可结合但不可交换,正是线段树需要的形态。 算法实现上分三步: 第一步,预处理 的幂到 ( 的三进制不超过 位),把每个 编码成 后自底向上建树。 第二步,处理修改操作,改掉对应叶子再沿父链逐层重算,每层做一次 。 第三步,处理询问操作,自底向上收集区间内的整块。左边界方向的块从左往右出现、右边界方向的块从右往左出现,两侧顺序相反,必须各用一个累加器攒着、最后合并一次,混用同一个累加器会把拼接顺序弄反。 可以是 ,而 的三进制写成一位的 而不是空串。编码时要单独挡出来,否则含 的区间会整体少一位,其后所有位的权重连锁错位。

时空复杂度分析

时间复杂度 :。建树时每个数转三进制约 位;之后每次修改与询问都只走 层,瓶颈是 次操作各自的这 层取模乘法。 空间复杂度 :。开销主要是 的幂表,要备到拼接后的总位数 ,线段树本身只有 。 Python

## 三进制翻转查询 - 线段树(区间合并)+ 三进制位权闭式
MOD = 998244353


## 把一个整数编码成 (三进制位数, 这一段单独解读出的数值)
## 推导:串 s_0..s_{L-1} 先互换 0/2(即 s -> 2-s)再首尾翻转,按三进制求值展开后
## 正好等于 sum_j (2 - s_j) * 3^j,也就是「左边第 j 位的权重是 3^j」。
## 有了这个闭式,每个元素就能脱离上下文单独预处理。
def encode(v):
    if v == 0:
        digits = [0]                      # 0 写成一位的 0,除法循环给不出这一位
    else:
        digits = []
        while v > 0:
            digits.append(v % 3)
            v //= 3
        digits.reverse()                  # 反转后 digits[j] 才是从左数第 j 位
    val = 0
    for j in range(len(digits) - 1, -1, -1):
        val = val * 3 + (2 - digits[j])   # 霍纳法从高 j 往低 j 累,等价于 sum (2-d_j)*3^j
    return len(digits), val % MOD


m, q = map(int, input().split())
v = list(map(int, input().split()))

## 1e9 的三进制最多 19 位,拼接后总长不超过 19*m,pow3 一次备齐,合并时 O(1) 取用
pow3 = [1] * (19 * m + 2)
for i in range(1, len(pow3)):
    pow3[i] = pow3[i - 1] * 3 % MOD

## 迭代式线段树:叶子层补齐到 2 的幂,避免递归写法在深度上冒风险
size = 1
while size < m:
    size <<= 1
t_len = [0] * (2 * size)
t_val = [0] * (2 * size)
for i in range(m):
    t_len[size + i], t_val[size + i] = encode(v[i])
## 自底向上建树。合并两段时右段整体向左平移了 A.len 位,所以要乘 3^{A.len}
for i in range(size - 1, 0, -1):
    t_len[i] = t_len[2 * i] + t_len[2 * i + 1]
    t_val[i] = (t_val[2 * i] + t_val[2 * i + 1] * pow3[t_len[2 * i]]) % MOD

for _ in range(q):
    op, a, b = map(int, input().split())
    if op == 2:
        # 单点修改:改掉叶子后一路向上重算,合并顺序不能交换(左右段权重不对称)
        i = size + a - 1
        t_len[i], t_val[i] = encode(b)
        i >>= 1
        while i >= 1:
            t_len[i] = t_len[2 * i] + t_len[2 * i + 1]
            t_val[i] = (t_val[2 * i] + t_val[2 * i + 1] * pow3[t_len[2 * i]]) % MOD
            i >>= 1
    else:
        # 区间查询:自底向上时命中的块,左边界给的是从左往右、右边界给的是从右往左,
        # 所以要分成左右两个累加器,最后再合并一次,才能保证拼接顺序与下标顺序一致
        left_len, left_val = 0, 0
        right_len, right_val = 0, 0
        lo = size + a - 1
        hi = size + b
        while lo < hi:
            if lo & 1:
                left_val = (left_val + t_val[lo] * pow3[left_len]) % MOD
                left_len += t_len[lo]
                lo += 1
            if hi & 1:
                hi -= 1
                right_val = (t_val[hi] + right_val * pow3[t_len[hi]]) % MOD
                right_len += t_len[hi]
            lo >>= 1
            hi >>= 1
        print((left_val + right_val * pow3[left_len]) % MOD)