大厂真题 / 百度

百度 7.16 笔试真题 - 研发岗

本场考试概述

考试时间:2026年7月16日

考试岗位:研发岗

难度评级:中等

考点分析

  • 第一题:周期模拟与取模(难度简单)
  • 第二题:线性不变量、向量空间与可行性判定(难度中等偏难)
  • 第三题:交替状态动态规划(难度中等)

建议策略

  • 第一题只记录到达每扇门时的时刻,用取模判断它处于开启段还是关闭段。
  • 第二题先寻找操作保持不变的量,再证明这些不变量不但必要,而且足以刻画所有可达状态。
  • 第三题要特别注意路径只能在走出边界时结束,不能因为后续贡献为负就主动停下。

第 1 题:断续传送门

题目描述

你需要依次经过 $n$ 扇传送门。所有传送门都从时刻 $t=0$ 开始运行。

第 $i$ 扇门会循环执行以下过程:

  • 开启 $a_i$ 秒;
  • 随后关闭 $b_i$ 秒;
  • 然后重新开启 $a_i$ 秒,如此循环。

你在 $t=0$ 时到达第一扇门。到达一扇门时:

  • 如果门正处于开启状态,就立即开始通过;
  • 如果门正处于关闭状态,就等待到它下一次开启,再开始通过。

通过每一扇门都需要恰好 $1$ 秒。通过第 $i$ 扇门后立即到达第 $i+1$ 扇门。求通过全部传送门的时刻。

在每个周期中,将相位记为 $r=t\bmod(a_i+b_i)$。当 $0\le r<a_i$ 时门开启;当 $a_i\le r<a_i+b_i$ 时门关闭。特别地,$r=a_i$ 的瞬间已经算作关闭。

输入描述

第一行输入一个整数 $T$,表示测试数据组数。

对于每组测试数据:

  • 第一行输入一个整数 $n$,表示传送门数量;
  • 接下来 $n$ 行,每行输入两个正整数 $a_i,b_i$。

数据范围:

  • $1\le T\le 10^6$;
  • 所有测试数据的 $n$ 之和不超过 $10^6$;
  • $1\le a_i,b_i\le 10^9$。

输出描述

对每组测试数据输出一行一个整数,表示通过全部传送门的时刻。

样例

输入

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

输出

5
6

样例解释

第一组中:

  • $t=0$ 时第一扇门开启,通过后为 $t=1$;
  • $t=1$ 时第二扇门关闭,等待到 $t=2$,通过后为 $t=3$;
  • $t=3$ 时第三扇门关闭,等待到 $t=4$,通过后为 $t=5$。

思路分析

time 表示到达当前传送门的时刻。对于参数为 $(a,b)$ 的门,其周期长度为

\[period=a+b.\]

计算当前时刻在周期内的相位:

\[remainder=time\bmod period.\]
  • 若 $remainder<a$,当前处于开启段,可以立刻通过;
  • 若 $remainder\ge a$,当前处于关闭段,距离下一周期开启还需等待 $period-remainder$ 秒。

完成可能的等待后,再把通过门所需的 $1$ 秒加入答案:

\[time\leftarrow time+1.\]

只需要按顺序模拟一次,无需逐秒推进。

正确性证明

依次考虑每一扇门。假设 time 是到达当前门的真实时刻。

  • time % (a + b) < a 时,按照门的周期定义,此刻门开启,最早且实际的通过完成时刻就是 time + 1
  • 否则门关闭,本周期内不会再次开启;下一次开启恰好发生在 time + (a + b - time % (a + b))。等待到该时刻并花费 $1$ 秒通过,得到的也是最早且实际的完成时刻。

因此,每次更新后 time 都等于通过当前门的真实最早时刻。由数学归纳法,扫描完所有门后,time 就是通过全部传送门的时刻。

题解代码

import sys


def solve():
    data = list(map(int, sys.stdin.buffer.read().split()))
    iterator = iter(data)
    test_count = next(iterator)
    answers = []

    for _ in range(test_count):
        n = next(iterator)
        time = 0

        for _ in range(n):
            open_time = next(iterator)
            close_time = next(iterator)
            period = open_time + close_time
            remainder = time % period

            if remainder >= open_time:
                time += period - remainder

            time += 1

        answers.append(str(time))

    sys.stdout.write('\n'.join(answers))


solve()

复杂度分析

设所有测试数据的传送门总数为 $N$。

时间复杂度:$O(N)$。

空间复杂度:除读入数据与输出外,额外空间为 $O(1)$。代码一次性读入输入,因此实际输入存储为 $O(N)$。


第 2 题:防火墙

题目描述

有一个长度为 $n$ 的实数序列 $w_1,w_2,\ldots,w_n$。你可以执行任意多次以下操作:

选择一个下标 $i$($1\le i\le n-2$)和任意实数 $x$,令连续三个元素发生变化:

\[(w_i,w_{i+1},w_{i+2}) \leftarrow (w_i-x,w_{i+1}+2x,w_{i+2}-x).\]

请判断是否能通过若干次操作,使序列中所有元素都非负。

操作过程中的元素可以为负,只要求最终序列全部非负。由于 $x$ 可以是任意实数,操作也允许取负的 $x$。

输入描述

第一行输入一个整数 $T$,表示测试数据组数。

对于每组测试数据:

  • 第一行输入一个整数 $n$;
  • 第二行输入 $n$ 个整数 $w_1,w_2,\ldots,w_n$。

数据范围:

  • $1\le T\le 10^4$;
  • 所有测试数据的 $n$ 之和不超过 $2\times 10^5$;
  • $-10^9\le w_i\le 10^9$。

输出描述

若可以使所有元素非负,输出 YES;否则输出 NO

样例

输入

4
3
-1 2 0
4
-2 2 2 0
3
1 -3 1
4
3 0 0 -1

输出

YES
YES
NO
NO

样例解释

  • 第一组取 $i=1,x=-1$,序列由 $(-1,2,0)$ 变成 $(0,0,1)$。
  • 第二组的总和 $S=2$,位置加权和 $Q=8$,满足 $0\le S\le Q\le 4S$,因此存在全非负的可达终态。
  • 第三组总和为 $-1$,不可能得到元素总和非负的全非负序列。
  • 第四组 $S=2,Q=-1$,而非负序列必须满足 $Q\ge S$,因此不可能。

思路分析

1. 找到两个不变量

定义序列总和

\[S=\sum_{j=1}^{n}w_j\]

和位置加权和

\[Q=\sum_{j=1}^{n}j\cdot w_j.\]

一次操作在位置 $i,i+1,i+2$ 上增加的向量是

\[x(-1,2,-1).\]

它对总和的改变量为

\[-x+2x-x=0,\]

对位置加权和的改变量为

\[-i x+2(i+1)x-(i+2)x=0.\]

所以无论进行多少次操作,$S$ 和 $Q$ 都保持不变。

2. 推出必要条件

假设最终得到全非负序列 $v_1,v_2,\ldots,v_n$。那么

\[S=\sum_{j=1}^{n}v_j\ge 0.\]

又因为每个位置 $j$ 都满足 $1\le j\le n$,且 $v_j\ge0$,所以

\[\sum_{j=1}^{n}v_j \le \sum_{j=1}^{n}jv_j \le n\sum_{j=1}^{n}v_j.\]

\[S\le Q\le nS.\]

因此必要条件是

\[\boxed{S\ge0\quad\text{且}\quad S\le Q\le nS}.\]

当 $S=0$ 时,这个条件自动要求 $Q=0$。

3. 证明条件足够:先构造一个非负目标

若 $S\ge0$ 且 $S\le Q\le nS$,可以只在位置 $1$ 和 $n$ 放置非负权重:

\[v_1=\frac{nS-Q}{n-1},\qquad v_n=\frac{Q-S}{n-1},\]

其余位置令 $v_j=0$。由 $S\le Q\le nS$ 可知 $v_1,v_n\ge0$,并且

\[v_1+v_n=S,\] \[v_1+nv_n=Q.\]

所以确实存在一个全非负序列,与原序列具有相同的 $S,Q$。

这里 $n=1$ 时无需使用上述分母:此时 $Q=S=w_1$,判定条件恰好退化为 $w_1\ge0$。

4. 证明同样的两个不变量足以保证可达

还必须排除一种可能:虽然找到了具有相同 $S,Q$ 的非负序列,但操作未必能到达它。

对每个 $1\le i\le n-2$,记一次单位操作向量为

\[e_i=(0,\ldots,0,-1,2,-1,0,\ldots,0).\]

这些向量都满足总和与位置加权和为 $0$,且它们线性无关:若

\[\sum_{i=1}^{n-2}c_i e_i=0,\]

观察第一个坐标可得 $c_1=0$;再依次观察第二、第三直到第 $n-2$ 个相关坐标,就能递推出所有 $c_i=0$。因此它们张成一个 $n-2$ 维空间。

另一方面,所有满足

\[\sum_j d_j=0,\qquad \sum_j j d_j=0\]

的变化向量 $d$ 构成两个独立线性约束的解空间,其维数同样为 $n-2$。操作向量张成的空间包含于该解空间,且维数相同,所以二者完全相等。

于是,只要两个序列的 $S,Q$ 相同,它们的差就一定能写成若干操作向量的实数线性组合。因为每次操作的 $x$ 可以取任意实数,这个线性组合可以逐项执行,故构造出的非负目标一定可达。

对于 $n=1$ 或 $n=2$,没有可执行的三元操作,但结论仍成立:$S,Q$ 已唯一确定整个序列。具体地,$n=2$ 时

\[w_1=2S-Q,\qquad w_2=Q-S,\]

条件 $S\le Q\le2S$ 正好等价于 $w_1,w_2\ge0$。

综上,判定条件既必要又充分。

正确性证明

算法计算原序列的 $S,Q$,并仅在 $S\ge0$ 且 $S\le Q\le nS$ 时输出 YES

  • 必要性:操作保持 $S,Q$ 不变;任意全非负终态都满足 $S\ge0$ 和 $S\le Q\le nS$。因此算法不会把不可行情况误判为 YES
  • 充分性:条件成立时,存在具有同样 $S,Q$ 的全非负目标序列;所有保持 $S,Q$ 的变化都由三元操作向量张成,因此该目标从原序列可达。算法不会把可行情况误判为 NO

故算法判定正确。

题解代码

import sys


def solve():
    data = list(map(int, sys.stdin.buffer.read().split()))
    iterator = iter(data)
    test_count = next(iterator)
    answers = []

    for _ in range(test_count):
        n = next(iterator)
        total = 0
        weighted_total = 0

        for position in range(1, n + 1):
            value = next(iterator)
            total += value
            weighted_total += position * value

        possible = (
            total >= 0
            and total <= weighted_total <= n * total
        )
        answers.append('YES' if possible else 'NO')

    sys.stdout.write('\n'.join(answers))


solve()

Python 整数不会溢出。若使用 64 位整数语言,$\lvert Q\rvert$ 在给定规模下可能达到约 $2\times10^{19}$,应使用 128 位整数计算加权和。

复杂度分析

设所有测试数据的元素总数为 $N$。

时间复杂度:$O(N)$。

空间复杂度:除读入数据与输出外,额外空间为 $O(1)$。


第 3 题:交替步长

题目描述

有一条长度为 $n$ 的数组 $A_1,A_2,\ldots,A_n$,以及两个正整数步长 $p,q$。

你可以任选一个位置作为起点,并任选 $p$ 或 $q$ 作为第一次移动的步长。将起点的权值计入路径和后,开始不断向右移动:

  • 如果第一次选择步长 $p$,之后的步长依次为 $p,q,p,q,\ldots$;
  • 如果第一次选择步长 $q$,之后的步长依次为 $q,p,q,p,\ldots$。

每到达一个仍在数组范围内的位置,就把该位置的权值加入路径和。某一步移动后位置超出数组右边界时,路径结束。

路径一旦开始,就必须按照交替规则一直移动到出界,不能在仍可移动到数组内时主动停止。求所有起点和两种首步选择中的最大路径和。

输入描述

第一行输入一个整数 $T$,表示测试数据组数。

对于每组测试数据:

  • 第一行输入三个整数 $n,p,q$;
  • 第二行输入 $n$ 个整数 $A_1,A_2,\ldots,A_n$。

数据范围:

  • $1\le T\le 10^5$;
  • 所有测试数据的 $n$ 之和不超过 $2\times10^5$;
  • $1\le p,q\le n$;
  • $-10^9\le A_i\le10^9$。

输出描述

对每组测试数据输出一行一个整数,表示最大路径和。

样例

输入

3
3 1 2
1 3 9
4 1 2
5 -100 6 6
4 1 1
10 -100 5 7

输出

12
17
12

样例解释

  • 第一组从位置 $2$ 出发并让首步为 $p=1$,访问 $2,3$,路径和为 $3+9=12$。
  • 第二组从位置 $1$ 出发并让首步为 $q=2$,访问 $1,3,4$,路径和为 $5+6+6=17$。
  • 第三组 $p=q=1$。从位置 $3$ 出发后必须继续走到位置 $4$,路径和为 $5+7=12$。

思路分析

1. 状态必须包含“下一步走多远”

只知道当前位置还不够,因为到达同一位置后,下一步可能应该走 $p$,也可能应该走 $q$。定义两个状态:

  • $f_p[i]$:从位置 $i$ 出发,计入 $A_i$,且下一步必须走 $p$ 时,直到出界的路径和;
  • $f_q[i]$:从位置 $i$ 出发,计入 $A_i$,且下一步必须走 $q$ 时,直到出界的路径和。

采用代码中的 $0$ 下标后,转移为

\[f_p[i]=A_i+ \begin{cases} f_q[i+p],&i+p<n,\\ 0,&i+p\ge n, \end{cases}\] \[f_q[i]=A_i+ \begin{cases} f_p[i+q],&i+q<n,\\ 0,&i+q\ge n. \end{cases}\]

因为转移只指向更大的下标,所以从右向左计算即可。

2. 为什么绝对不能写 max(0, 后续状态)

本题只有“下一步走出右边界”才能结束路径。如果下一步仍在数组内,即使后续路径和为负,也必须继续走。

因此下面这种常见写法是错误的:

f_p[i] = A[i] + max(0, f_q[i + p])

max(0, ...) 等价于允许玩家在位置 $i$ 主动停止,改变了题意。

例如 $n=2,p=q=1,A=[100,-1000]$。从位置 $1$(代码下标 $0$)出发必须访问两个位置,路径和为 $-900$,不能只拿到 $100$ 就停止;从位置 $2$ 出发得到 $-1000$,所以该例的真正答案是 $-900$。错误转移却会虚构出答案 $100$。

只有当 i + step >= n、即这一步会直接出界时,后续贡献才是 $0$。

3. 枚举所有合法起始状态

题目允许任选起点,也允许首步选择 $p$ 或 $q$。所以最终答案为

\[\max_{0\le i<n}\{f_p[i],f_q[i]\}.\]

即使所有 $A_i$ 都是负数,也必须选择一个起点,因此答案不能初始化为 $0$,而应初始化为负无穷或直接取状态数组的最大值。

正确性证明

按下标从右到左进行归纳。

对于位置 $i$:

  • 若下一步 $p$ 会出界,则以 $p$ 为下一步的路径只访问位置 $i$,故 $f_p[i]=A_i$;
  • 若 $i+p<n$,规则要求必须移动到 $i+p$,之后下一步切换为 $q$。路径不能提前停止,因此完整路径和唯一地等于 $A_i+f_q[i+p]$。

这正是 $f_p[i]$ 的转移。对 $f_q[i]$ 同理。由于依赖位置都严格位于 $i$ 的右侧,逆序计算时依赖状态已经正确,于是归纳得到所有状态均表示对应规则下直到出界的真实路径和。

每条合法路径都唯一对应某个起点及某种首步选择,也就是某个 $f_p[i]$ 或 $f_q[i]$;反之每个状态也对应一条合法路径。因此取所有状态的最大值,恰好得到题目要求的最大路径和。

题解代码

import sys


def solve():
    data = list(map(int, sys.stdin.buffer.read().split()))
    iterator = iter(data)
    test_count = next(iterator)
    answers = []

    for _ in range(test_count):
        n = next(iterator)
        p = next(iterator)
        q = next(iterator)
        values = [next(iterator) for _ in range(n)]

        dp_p = [0] * n
        dp_q = [0] * n

        for i in range(n - 1, -1, -1):
            dp_p[i] = values[i]
            if i + p < n:
                dp_p[i] += dp_q[i + p]

            dp_q[i] = values[i]
            if i + q < n:
                dp_q[i] += dp_p[i + q]

        answer = max(max(dp_p), max(dp_q))
        answers.append(str(answer))

    sys.stdout.write('\n'.join(answers))


solve()

复杂度分析

设所有测试数据的数组长度之和为 $N$。

时间复杂度:$O(N)$。

空间复杂度:每组 $O(n)$,用于两个动态规划数组。


小结

  • 第一题通过 time % (a + b) 直接定位门在当前周期中的状态,只在关闭时跳到下一周期开头。
  • 第二题的完整结论是:存在全非负可达终态,当且仅当 $S\ge0$ 且 $S\le Q\le nS$。关键不仅是找到两个不变量,还要证明操作向量张成了保持这两个量不变的全部变化。
  • 第三题需要为“下一步走 $p$”和“下一步走 $q$”分别建状态。路径必须持续到出界,所以合法转移中不能出现 max(0, ...),全负数组时答案也不能默认为 $0$。