大厂真题

携程技术岗 2026-09-06

本场考试概述

考试时间:2026-9-6

考试岗位:技术岗

难度评级:中等

考点分析

第一题:栈(简单)

第二题:调和级数分块 + 前缀和(困难)

建议策略

只有两道题,第一题必须稳拿。它的题面把配对规则写得很长,但”左边最近的、尚未配对的左括号”就是栈顶,看出这一点后一趟扫描即可,注意内部长度为 数学公式(保留原始 SVG) 的相邻括号在任何 数学公式(保留原始 SVG) 下都要计入。

第二题是本场的分水岭。按定义逐个算是 数学公式(保留原始 SVG) 次运算,必须先把 数学公式(保留原始 SVG) 拆成 数学公式(保留原始 SVG),再利用 数学公式(保留原始 SVG) 在整段上取值相同的性质配前缀和分块。总段数是调和级数量级,这个套路在数论与计数题里复用度很高,值得专门练。


第 1 题:括号配对计数

题目描述

小明 拿到了一个长度为 数学公式(保留原始 SVG) 的括号序列 数学公式(保留原始 SVG),它只由字符 () 组成,并且保证是合法的:从左往右扫描它的任意一个前缀时,( 出现的次数都不少于 ) 出现的次数;扫描完整个序列后,两种字符出现的次数相等。

在合法序列中,每个 ) 都与它左边最近的、尚未被配对的 ( 组成一对。设某一对括号所在的位置分别是 数学公式(保留原始 SVG)数学公式(保留原始 SVG),把它们之间的字符个数称为这对括号的内部长度,即 数学公式(保留原始 SVG),也就是位置 数学公式(保留原始 SVG) 上的字符数量。

给定一个正整数 数学公式(保留原始 SVG),小明 想知道内部长度能被 数学公式(保留原始 SVG) 整除的括号对有多少个。注意 数学公式(保留原始 SVG) 能被任意正整数整除。

输入描述

第一行输入两个整数 数学公式(保留原始 SVG),分别表示括号序列的长度和整除的模数,保证 数学公式(保留原始 SVG) 是偶数。

第二行输入一个长度为 数学公式(保留原始 SVG) 的字符串 数学公式(保留原始 SVG),只由字符 () 组成,保证是合法的括号序列。

输出描述

输出一个整数,表示内部长度能被 数学公式(保留原始 SVG) 整除的括号对数量。

样例1

输入

2 1
()

输出

1

样例解释

唯一的一对括号位于位置 数学公式(保留原始 SVG)数学公式(保留原始 SVG),内部长度为 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 能被 数学公式(保留原始 SVG) 整除,因此答案为 数学公式(保留原始 SVG)

样例2

输入

6 3
(()())

输出

2

样例解释

三对括号的内部长度依次为:位置 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 配对,内部长度 数学公式(保留原始 SVG);位置 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 配对,内部长度 数学公式(保留原始 SVG);位置 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 配对,内部长度 数学公式(保留原始 SVG)。其中 数学公式(保留原始 SVG) 能被 数学公式(保留原始 SVG) 整除而 数学公式(保留原始 SVG) 不能,因此答案为 数学公式(保留原始 SVG)

样例3

输入

8 5
((()()))

输出

2

样例解释

四对括号的内部长度依次为 数学公式(保留原始 SVG)。只有两个 数学公式(保留原始 SVG) 能被 数学公式(保留原始 SVG) 整除,数学公式(保留原始 SVG)数学公式(保留原始 SVG) 都不能,因此答案为 数学公式(保留原始 SVG)

题解:栈

思路分析

给定一个合法括号串,每个 ) 与左边最近的、尚未配对的 ( 配成一对,位置 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 的这一对内部长度为 数学公式(保留原始 SVG),求内部长度能被 数学公式(保留原始 SVG) 整除的对数。

这是一道配对规则直接决定数据结构的题:计数本身只是一次取模判断,难点在于看出”左边最近的、尚未配对的”这句话说的就是栈顶。

算法实现

先看最直白的想法。对每个 ) 从它的位置往左找配对的 (,途中还要跳过已经配好对的那些,最坏每个右括号都要回扫大半个串,总量 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 取到 数学公式(保留原始 SVG) 时约 数学公式(保留原始 SVG) 次比较,远超时限。

问题在于左括号的信息被反复重扫,其实可以边走边攒。配对规则里的”最近”与”尚未配对”合起来说的是:左括号一旦被配走就永久出局,且总是最晚出现的那个未配对左括号最先被配走。后进先出,这正是栈。

于是扫描只需一趟。遇到 ( 就把它的下标压栈,遇到 ) 就弹出栈顶下标 数学公式(保留原始 SVG),当前下标即 数学公式(保留原始 SVG),两者当场配成一对,判一次

数学公式(保留原始 SVG)

成立就把答案加一。题面保证串合法,所以每次遇到 ) 时栈一定非空,不必额外判空。

最容易漏的输入是内部长度为 数学公式(保留原始 SVG) 的那些对。相邻的 ()数学公式(保留原始 SVG),而 数学公式(保留原始 SVG) 能被任何正整数整除,所以它们在任何 数学公式(保留原始 SVG) 下都要计入;串 ()()() 全由这类对组成,答案恒为 数学公式(保留原始 SVG),这也是本题答案的上界。

复杂度分析

时间复杂度数学公式(保留原始 SVG)。每个字符只被访问一次,至多入栈、出栈各一次,取模是常数操作。读入串本身就要 数学公式(保留原始 SVG),这个量级已经到底。

空间复杂度数学公式(保留原始 SVG)。栈中存放未配对左括号的下标,全嵌套串 ((())) 时栈深达到 数学公式(保留原始 SVG)

题解代码

import sys
input = sys.stdin.readline

def count_pairs(t, m):
    stack = []
    ans = 0
    for r, ch in enumerate(t):
        if ch == '(':
            stack.append(r)
        else:
            l = stack.pop()
            if (r - l - 1) % m == 0:
                ans += 1
    return ans

n, m = map(int, input().split())
t = input().strip()
print(count_pairs(t, m))

正确性说明

栈中按顺序保存所有尚未匹配的左括号,当前右括号的匹配对象恰好是栈顶。每一对在弹栈时计算一次内部长度,故没有漏计和重复计数。

易错点与边界

内部长度是 r-l-1 而不是 r-l+1;零可被任意正整数整除。

第 2 题:余数加权和

题目描述

小明 有一个长度为 数学公式(保留原始 SVG) 的数组 数学公式(保留原始 SVG),下标从 数学公式(保留原始 SVG) 开始编号,依次为 数学公式(保留原始 SVG)

对于一个正整数 数学公式(保留原始 SVG),小明 定义这个数组在模数 数学公式(保留原始 SVG) 下的加权和 数学公式(保留原始 SVG) 为:把每个下标 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 取余得到的结果乘上 数学公式(保留原始 SVG),再把所有乘积相加,即

数学公式(保留原始 SVG)

请依次求出 数学公式(保留原始 SVG) 的值。由于结果可能很大,请把每个值都对 数学公式(保留原始 SVG) 取模后输出。

输入描述

第一行输入一个整数 数学公式(保留原始 SVG),表示数组的长度。

第二行输入 数学公式(保留原始 SVG) 个整数 数学公式(保留原始 SVG),表示数组中的元素。

输出描述

输出一行 数学公式(保留原始 SVG) 个整数,依次表示 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 取模后的结果,相邻两个整数之间用一个空格分隔。

样例1

输入

4
3 1 4 2

输出

0 3 9 15

样例解释

数学公式(保留原始 SVG) 中任何下标对 数学公式(保留原始 SVG) 取余都是 数学公式(保留原始 SVG),总和为 数学公式(保留原始 SVG)数学公式(保留原始 SVG)数学公式(保留原始 SVG)数学公式(保留原始 SVG)

样例2

输入

1
8

输出

0

样例解释

数组中只有下标为 数学公式(保留原始 SVG) 的一个元素,数学公式(保留原始 SVG)

样例3

输入

6
2 0 5 1 0 3

输出

0 4 16 16 13 28

样例解释

数学公式(保留原始 SVG) 仍然是 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 对应的余数序列是 数学公式(保留原始 SVG),加权和为 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 对应的余数序列是 数学公式(保留原始 SVG),加权和为 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 对应的余数序列是 数学公式(保留原始 SVG),加权和为 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 对应的余数序列是 数学公式(保留原始 SVG),加权和为 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 中所有下标都小于 数学公式(保留原始 SVG),加权和就是 数学公式(保留原始 SVG)

题解:调和级数分块 + 前缀和

思路分析

给定长度为 数学公式(保留原始 SVG) 的数组 数学公式(保留原始 SVG),对每个 数学公式(保留原始 SVG)数学公式(保留原始 SVG)数学公式(保留原始 SVG),求下标对 数学公式(保留原始 SVG) 取余后再与 数学公式(保留原始 SVG) 相乘的总和,一共输出 数学公式(保留原始 SVG) 个结果。

这是一道靠恒等变形换算法的题:按定义逐个算需要 数学公式(保留原始 SVG) 轮、每轮 数学公式(保留原始 SVG) 次取余,数学公式(保留原始 SVG) 取到 数学公式(保留原始 SVG) 时是 数学公式(保留原始 SVG) 次运算,必须把取余改写成可以整段处理的形式。

算法实现

先把取余拆开。对任意正整数 数学公式(保留原始 SVG)数学公式(保留原始 SVG),代入定义式得

数学公式(保留原始 SVG)

左边那项与 数学公式(保留原始 SVG) 无关,记

数学公式(保留原始 SVG)

它只需在读入时算一次,此后每个 数学公式(保留原始 SVG) 直接取用。

再看右边那项。数学公式(保留原始 SVG) 不像 数学公式(保留原始 SVG) 那样每步都变,它在 数学公式(保留原始 SVG) 落入 数学公式(保留原始 SVG) 的整段区间上恒等于 数学公式(保留原始 SVG),整个下标范围只被切成 数学公式(保留原始 SVG) 段。段内的系数既然固定,需要的就只是段内 数学公式(保留原始 SVG) 之和,取前缀和

数学公式(保留原始 SVG)

即可 数学公式(保留原始 SVG) 拿到。于是右边那项写成按段累加的形式:

数学公式(保留原始 SVG)

数学公式(保留原始 SVG) 的那一段系数为 数学公式(保留原始 SVG),扫描从 数学公式(保留原始 SVG) 的起点 数学公式(保留原始 SVG) 开始即可;末段可能不满 数学公式(保留原始 SVG) 个下标,右端点截到 数学公式(保留原始 SVG)

这套做法之所以跑得动,在于总段数是调和级数:对固定的 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 段,所有 数学公式(保留原始 SVG) 加起来约 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 时约 数学公式(保留原始 SVG) 段,比朴素解低了四个数量级。

取模有两处要留意。数学公式(保留原始 SVG) 与段和都可达 数学公式(保留原始 SVG) 与模数量级,乘积到 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 同理,C++、Java、Go 必须全程用 64 位整数并逐步取模。数学公式(保留原始 SVG) 在模意义下可能为负,要写成 数学公式(保留原始 SVG) 修正;Python 的 % 对负数已返回非负,不必额外处理。

复杂度分析

时间复杂度数学公式(保留原始 SVG)。瓶颈是对每个 数学公式(保留原始 SVG) 的分段循环,总段数 数学公式(保留原始 SVG) 是调和级数量级;前缀和与 数学公式(保留原始 SVG) 的预处理只有 数学公式(保留原始 SVG)

空间复杂度数学公式(保留原始 SVG)。前缀和数组与存放 数学公式(保留原始 SVG) 个答案的数组各占一份。

题解代码

import sys
input = sys.stdin.readline

MOD = 10 ** 9 + 7

def harmonic_sums(n, v):
    """求 H(1), H(2), ..., H(n) 对 MOD 取模的结果"""
    P = [0] * (n + 1)
    S = 0
    s = 0
    for i in range(n):
        s += v[i]
        if s >= MOD:
            s -= MOD
        P[i + 1] = s
        S = (S + i * v[i]) % MOD

    res = [0] * n
    for d in range(1, n + 1):
        t = 0
        k = 1
        start = d          # k=0 那一段乘的是 0,直接从 k=1 的起点 d 开始扫
        while start < n:
            end = start + d
            if end > n:
                end = n    # 最后一段可能不满 d 个,右端点截到 n
            t = (t + k * (P[end] - P[start])) % MOD
            k += 1
            start += d
        res[d - 1] = (S - d * t) % MOD
    return res

n = int(input())
v = list(map(int, input().split()))
print(' '.join(map(str, harmonic_sums(n, v))))

正确性说明

将 i mod d 写成 i-d floor(i/d) 是恒等变换。每个整除商分段内的系数固定,前缀和精确求出段内权值和;各段不交且覆盖所有非零商下标,故分块总和等于定义式。

易错点与边界

下标从 0 开始;最后不足 d 长度的分段必须截断;模意义前缀差可能为负,Python 的正模数取余可正确归一化。

小结

优先从题面约束提炼模型,再用样例检查边界。本文保留题面数学符号的原始 SVG,代码统一为 Python 3;未给出的评测限制或规则不补作事实。