大厂真题 / 携程

携程 2026 春招笔试真题 - 研发岗(3.29)

本场考试概述

考试时间:2026年3月29日 考试岗位:研发岗 难度评级:中等偏难

考点分析

  1. 第一题:模拟(难度简单)
  2. 第二题:模拟 + 排名维护(难度中等)
  3. 第三题:后缀数组 + 贪心(难度困难)
  4. 第四题:质数筛 + 分段跳跃(难度困难)

建议策略

  • 前两题是纯模拟,务必快速拿满分
  • 第三题后缀数组是字符串进阶考点,关键在于理解后缀排序与贪心分配的关系
  • 第四题数论味很浓,需要分析 gcd 的周期性质来优化迭代

第一题:在哪里呢

题目描述

给定一个长度为 5 的字符串,保证其中恰好包含一个字符 'a'。请输出 'a' 在字符串中的位置(1-indexed)。

样例

输入

bxaxz

输出

3

思路分析

逐字符扫描,找到 'a' 就返回其下标 + 1。字符串长度固定为 5,直接线性遍历即可。没有任何坑点,属于签到题。

时间复杂度:O(1)(字符串长度固定为 5)。 空间复杂度:O(1)。

import sys
input = sys.stdin.readline

s = input().strip()
for i in range(5):
    if s[i] == 'a':
        print(i + 1)
        break

第二题:实时排名

题目描述

IOI 赛制评分系统。共有 n 次提交,每次提交格式为 (user, problem, score)。每道题目的得分取该用户在该题所有提交中的最高分。用户的总分为所有题目得分之和。

每次提交后,输出用户 1 的实时排名。排名定义:总分严格高于用户 1 的人数 + 1。

样例

输入

10
2 1 0
1 1 80
3 2 100
1 2 60
3 1 40
3 3 60
1 1 90
5 1 100
5 2 100
1 4 50

输出

1 1 2 1 1 2 2 2 3 1

思路分析

本题的核心在于高效维护每个用户的”每题最高分”和”总分”。

关键观察:每次提交只会影响一个用户的一道题。如果本次提交的分数高于该用户该题的历史最高分,就更新最高分,同时更新该用户的总分(加上差值)。如果分数没有提高,总分不变。

排名计算:每次提交后,遍历所有已出现的用户,统计总分严格大于用户 1 的人数,加 1 即为排名。由于 n 最多是常规笔试范围(约 10^5),且每次只需遍历已有用户数,直接暴力统计即可。

具体实现使用两层字典:best[user][problem] 记录最高分,total[user] 记录总分。每次提交时先算差值 delta = max(0, score - best[user][problem]),再更新 total[user] += delta

时间复杂度:O(n^2) 最坏情况(每次遍历所有用户),但笔试数据规模下完全够用。 空间复杂度:O(n)。

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

n = int(input())
best = defaultdict(lambda: defaultdict(int))  # best[user][problem]
total = defaultdict(int)                       # total[user]
results = []

for _ in range(n):
    u, p, s = map(int, input().split())
    old = best[u][p]
    if s > old:
        total[u] += s - old
        best[u][p] = s

    # 计算用户 1 的排名
    score1 = total[1]
    rank = 1
    for uid in total:
        if uid != 1 and total[uid] > score1:
            rank += 1
    results.append(str(rank))

print(" ".join(results))

第三题:字符串min

题目描述

给定一个长度为 n 的字符串 s 和一个长度为 n 的正整数数组 a。你可以将 a 重新排列为 a’,然后构造字符串 T = s[0] * a’[0] + s[1] * a’[1] + … + s[n-1] * a’[n-1](即每个字符重复 a’[i] 次后拼接)。请输出使 T 字典序最小的排列 a’。

样例

输入

3
bac
1 2 3

输出

3 1 2

思路分析

这道题乍看是一个排列优化问题,但核心思想可以通过后缀比较 + 贪心来解决。

第一步:理解目标。T 由 s[0] 重复 a’[0] 次、s[1] 重复 a’[1] 次、… 拼接而成。我们希望 T 的字典序尽可能小。直觉上,如果某个位置 i 的后续部分(从位置 i 开始到末尾的后缀)在字典序上越”小”,那么分配给它的重复次数应该越”大”——因为重复次数大意味着这个字符占据更长的前缀位置,而一个字典序小的后缀占据更多位置不会使 T 变大。

第二步:为什么用后缀排序。考虑两个位置 i 和 j,假设 suffix(i) < suffix(j)(字典序)。如果 a’[i] 更大,则位置 i 的字符 s[i] 会重复更多次。由于 suffix(i) 整体更小,让它”扩展”更长不会让 T 变差。反之,如果把大的 a’ 值分配给后缀字典序大的位置,T 的后续部分会更早出现大的字符序列。

更严格地说,后缀数组给出了所有后缀的字典序排名。排名越小(后缀越小),分配的 a’ 值应该越大。这是因为:

  • 排名小的后缀意味着从该位置往后看,字符串整体偏小
  • 给它分配大的重复次数,相当于让这个”小”的模式在 T 中占据更多空间
  • 这正好使得 T 的字典序最小

第三步:实现细节

  1. 对字符串 s 计算后缀数组(Suffix Array),得到每个位置的后缀排名 rank[i]
  2. 将 a 数组排序(降序)
  3. 按后缀排名从小到大的顺序,依次分配最大的 a 值、次大的 a 值、…
  4. 即:后缀排名第 1 小的位置得到 a 中最大值,后缀排名第 2 小的位置得到次大值,以此类推

这里使用倍增法构建后缀数组,时间复杂度 O(n log n)。

时间复杂度:O(n log n)(后缀数组构建) + O(n log n)(排序) = O(n log n)。 空间复杂度:O(n)。

import sys
input = sys.stdin.readline

def suffix_array(s):
    """倍增法构建后缀数组,返回 sa 和 rank 数组"""
    n = len(s)
    sa = list(range(n))
    rank = [ord(c) for c in s]
    tmp = [0] * n
    k = 1
    while k < n:
        def cmp_key(i):
            return (rank[i], rank[i + k] if i + k < n else -1)
        sa.sort(key=cmp_key)
        tmp[sa[0]] = 0
        for i in range(1, n):
            tmp[sa[i]] = tmp[sa[i - 1]]
            if cmp_key(sa[i]) != cmp_key(sa[i - 1]):
                tmp[sa[i]] += 1
        rank = tmp[:]
        if rank[sa[-1]] == n - 1:
            break
        k *= 2
    return sa, rank

def solve():
    n = int(input())
    s = input().strip()
    a = list(map(int, input().split()))

    sa, rank = suffix_array(s)

    # 将 a 降序排列
    a_sorted = sorted(a, reverse=True)

    # sa[i] 是排名第 i 的后缀的起始位置
    # 排名第 0 小的后缀分配最大值,排名第 1 小的分配次大值...
    ans = [0] * n
    for i in range(n):
        ans[sa[i]] = a_sorted[i]

    print(" ".join(map(str, ans)))

solve()

第四题:min和gcd

题目描述

定义函数 f(x) = min(x + gcd(x, b), b + gcd(x, b))。给定 x, b, n,求 f 迭代 n 次后的值,即 f^n(x)。

多组测试数据。

样例

输入

2
7 3 4
10 2 3

输出

4
4

思路分析

这道题需要仔细分析函数 f 的行为,找到优化迭代的方法。

第一步:理解 f(x) 的行为

f(x) = min(x + gcd(x, b), b + gcd(x, b))

设 g = gcd(x, b),则 f(x) = min(x + g, b + g) = g + min(x, b)。

  • 当 x <= b 时:f(x) = g + x = x + gcd(x, b)
  • 当 x > b 时:f(x) = g + b = b + gcd(x, b)

第二步:分析 x >= b 的情况

若 x >= b,则 f(x) = b + gcd(x, b)。注意 gcd(x, b) 是 b 的因子(因为 gcd(x, b) b),所以 f(x) = b + d,其中 d b。
接下来 f(b + d):gcd(b + d, b) = gcd(d, b) = d(因为 d b),所以 f(b + d) = b + d + d = b + 2d?不对,再看:min(b + d + d, b + d) = b + d。所以 f(b + d) = b + d。

这说明一旦 x >= b,再迭代一次就到达不动点 b + gcd(x, b),之后不再变化。更准确地说:当 x >= b 时,f(x) = b + gcd(x, b),且这个值本身也 >= b,所以 f(f(x)) = f(b + gcd(x, b)) = b + gcd(b + gcd(x,b), b) = b + gcd(x, b) = f(x)。因此 x >= b 后最多再迭代一次就稳定。

第三步:分析 x < b 的核心情况

当 x < b 时,f(x) = x + gcd(x, b)。设 g = gcd(x, b),则 x’ = x + g。

关键问题是:从 x 开始,每次加 gcd(当前值, b),需要多少步才能使得 x >= b?

每一步的增量 gcd(x, b) 取决于 x 和 b 的公因子。如果 b 只有少量不同的质因子,那么 gcd(x, b) 的可能取值也有限。

第四步:分段跳跃优化

考虑 b 的质因子分解 b = p1^e1 * p2^e2 * … * pk^ek。gcd(x, b) 的值取决于 x 包含了 b 的哪些质因子的幂次。

核心观察:当 x 是 b 的某个因子 d 的倍数时,gcd(x, b) 至少为 d,每步至少增加 d。从 x 到下一个 x 满足更高 gcd 的过程中,增量是固定的(等于当前的 gcd(x, b))。

因此可以这样优化:

  1. 求出 b 的所有因子并排序
  2. 对于当前的 x,计算 g = gcd(x, b)
  3. x 每次增加 g,直到 gcd 发生变化或 x >= b
  4. 在 gcd 不变的区间内,可以直接算出需要多少步跳到下一个 gcd 变化点,然后一次跳完

具体来说,如果当前 gcd(x, b) = g 且 g 固定不变,x 每次 +g,那么 x 经过 t 步变为 x + tg。我们需要找到最小的 t 使得 gcd(x + tg, b) != g 或 x + t*g >= b。

gcd 什么时候会变化?当 x + t*g 成为 b 的某个更大因子的倍数时。枚举 b 的所有大于 g 的因子 d,计算最小的 t 使得 d (x + t*g)。取所有这些 t 的最小值即为跳跃步数。

由于 b 的因子个数通常不大(约 O(sqrt(b)) 级别),每次跳跃后 gcd 至少翻倍(变为更大的因子),所以跳跃次数是 O(log b) 级别。总复杂度远优于逐步模拟。

时间复杂度:O(sqrt(b) + d(b) * log(b)),其中 d(b) 是 b 的因子个数。 空间复杂度:O(d(b))。

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

def get_divisors(b):
    """获取 b 的所有因子,升序排列"""
    divs = []
    i = 1
    while i * i <= b:
        if b % i == 0:
            divs.append(i)
            if i != b // i:
                divs.append(b // i)
        i += 1
    divs.sort()
    return divs

def solve():
    x, b, n = map(int, input().split())

    for _ in range(n):
        if x >= b:
            # 不动点: f(x) = b + gcd(x, b), 之后不再变化
            x = b + gcd(x, b)
            break

        g = gcd(x, b)

        # x < b: f(x) = x + g
        # 计算在 gcd 不变的情况下可以跳多少步
        # x, x+g, x+2g, ... 直到 gcd 变化或 x+t*g >= b
        steps_to_b = (b - x - 1) // g  # 最多这么多步还保持 x < b

        # 找最小步数使 gcd 变化: 枚举 b 的因子中大于 g 的
        divs = get_divisors(b)
        min_steps = steps_to_b + 1  # 默认跳到 >= b

        for d in divs:
            if d <= g:
                continue
            # 找最小 t >= 1 使得 d | (x + t * g)
            rem = x % d
            if rem == 0:
                # 已经整除,但 gcd(x,b) = g < d,说明 d 不整除 b 或
                # x 包含 d 但 gcd(x,b) 不等于 d —— 不可能,因为 d|b 且 d|x 则 d|gcd(x,b)
                continue
            need = d - rem  # 需要增加 need 使得 (x + need) % d == 0
            if need % g != 0:
                continue
            t = need // g
            if t == 0:
                t = d // g  # 下一个周期
            if t < min_steps:
                min_steps = t

        # 实际可跳步数受剩余 n 限制
        actual = min(min_steps, n - (_ + 1 - 1))
        # 重新计算: 当前是第 _+1 步(从0开始计数的第_步),还剩 n-1-_ 步可用
        # 但我们在循环里逐步处理,这里需要改为批量跳跃

        # 简化:直接跳 min_steps 步或剩余步数
        break  # 这个方法在循环结构里不好写,改用 while 循环

    print(x)

def solve_case():
    x, b, n = map(int, input().split())

    step = 0
    while step < n:
        if x >= b:
            g = gcd(x, b)
            x = b + g  # 不动点
            step += 1
            break

        g = gcd(x, b)
        # x < b, 每次 x += g (在 g 不变期间)
        # 还需 n - step 步

        remaining = n - step

        # 最多跳多少步 x 还 < b
        steps_to_b = (b - x - 1) // g

        # 找最小步数使 gcd 变化
        divs = get_divisors(b)
        min_change = steps_to_b + 1

        for d in divs:
            if d <= g:
                continue
            rem = x % d
            if rem == 0:
                continue
            need = d - rem
            if need % g != 0:
                continue
            t = need // g
            if t == 0:
                t = d // g
            if 0 < t < min_change:
                min_change = t

        jump = min(min_change, remaining)
        x += jump * g
        step += jump

    print(x)

t = int(input())
for _ in range(t):
    solve_case()

样例验证

样例 1:x=7, b=3, n=4

  • 步骤 1:x=7 >= b=3,gcd(7,3)=1,f(7) = min(8, 4) = 4
  • 步骤 2:x=4 >= b=3,gcd(4,3)=1,f(4) = min(5, 4) = 4(不动点)
  • 步骤 3:f(4) = 4
  • 步骤 4:f(4) = 4
  • 输出 4

样例 2:x=10, b=2, n=3

  • 步骤 1:x=10 >= b=2,gcd(10,2)=2,f(10) = min(12, 4) = 4
  • 步骤 2:x=4 >= b=2,gcd(4,2)=2,f(4) = min(6, 4) = 4(不动点)
  • 步骤 3:f(4) = 4
  • 输出 4

小结

  • 第一题纯签到,遍历 5 个字符找 'a' 即可
  • 第二题 IOI 赛制模拟,维护每题最高分和用户总分,每次提交后暴力统计排名
  • 第三题后缀数组 + 贪心是字符串竞赛经典套路:后缀排名小的位置分配大的重复次数,使拼接结果字典序最小
  • 第四题分析 f(x) 的数学结构是关键:x >= b 时一步到达不动点,x < b 时利用 gcd 的因子结构做分段跳跃,避免逐步模拟