大厂真题 / 上海AILab

上海AILab 5.16 笔试真题 - 通用岗

本场考试概述

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

考点分析

  1. 第一题:井字棋模拟(难度简单)
  2. 第二题:排序贪心 + 前缀和(难度中等)
  3. 第三题:数论 + 质因数分解 + 计数原理(难度中等)

建议策略

  1. 第一题是纯模拟题,按落子顺序维护棋盘状态并逐步检查违规和胜负即可,优先稳拿满分
  2. 第二题核心洞察是”每轮存活前一半”——排序后用前缀和累加即可
  3. 第三题将 $\text{lcm}(a, b) = x$ 转化为每个质因数上的指数约束,再用乘法原理计数

第一题:井字棋

题目描述

在一个 $3 \times 3$ 的棋盘上,两位玩家依次下子,先手执 “X”,后手执 “O”。棋盘格子用行坐标 $x$ 和列坐标 $y$ 表示,均从 $1$ 开始编号。给出完整的落子过程,判断这盘棋的最终结果:

  • 若存在不合法操作(包括在一方胜利后仍继续落子,或下在已有棋子的位置),输出 $-1$
  • 若先手获胜(形成三子连线),输出 $0$
  • 若后手获胜,输出 $1$
  • 若处理完所有落子后无人获胜且不存在不合法操作,输出 $2$

第一行输入整数 $T$ 表示测试组数。每组第一行输入 $n$ 表示总落子次数,接下来 $n$ 行每行输入 $x, y$ 表示落子位置。奇数步为先手下 “X”,偶数步为后手下 “O”。

样例

输入

4
5
1 1
2 1
1 2
2 2
1 3
6
1 1
2 1
1 3
2 2
3 1
2 3
3
1 1
1 1
2 2
9
1 1
1 2
1 3
2 3
2 1
2 2
3 2
3 1
3 3

输出

0
1
-1
2

思路分析

第一步:明确需要维护的状态

这是一道模拟题,核心在于按照游戏规则逐步执行并检测两类违规:

  • 重叠落子:目标格已有棋子
  • 胜后继续:有人已经赢了还在落子

同时还需要在每次合法落子后检查是否产生了胜者。

第二步:高效的胜负判定

$3 \times 3$ 棋盘上,三子连线只可能出现在 8 条线上(3行 + 3列 + 2对角线)。但每次落子只需要检查经过该点的线(最多4条),不需要遍历全部8条。

第三步:处理优先级

违规优先于胜负:如果整个过程中出现了任何违规操作,不管有没有人赢,都输出 $-1$。一旦检测到违规就标记,但仍需继续消耗剩余输入。

题解代码

import sys
input = sys.stdin.readline

def solve():
    T = int(input())
    for _ in range(T):
        n = int(input())
        board = [['.'] * 3 for _ in range(3)]
        illegal = False
        won = False
        winner = '.'
        for i in range(n):
            x, y = map(int, input().split())
            if illegal:
                continue
            if won:
                illegal = True
                continue
            r, c = x - 1, y - 1
            if board[r][c] != '.':
                illegal = True
                continue
            p = 'X' if i % 2 == 0 else 'O'
            board[r][c] = p
            # 检查经过 (r, c) 的行、列、对角线
            if (board[r][0] == board[r][1] == board[r][2] == p or
                board[0][c] == board[1][c] == board[2][c] == p or
                (r == c and board[0][0] == board[1][1] == board[2][2] == p) or
                (r + c == 2 and board[0][2] == board[1][1] == board[2][0] == p)):
                won = True
                winner = p
        if illegal:
            print(-1)
        elif won:
            print(0 if winner == 'X' else 1)
        else:
            print(2)

solve()

复杂度分析

时间复杂度:$O(T \cdot n)$,每步落子检查常数条线,单组 $O(n)$ 空间复杂度:$O(1)$,仅维护一个 $3 \times 3$ 棋盘


第二题:单败淘汰赛

题目描述

你要举办一场单败淘汰赛,共有 $n$ 支队伍,第 $i$ 支队伍的能力值为 $a_i$,保证任意两队能力值不同。

比赛按轮进行:每一轮,若剩余队伍为偶数,将它们两两配对;若为奇数,任选一支轮空直接晋级,其余两两配对。你可以自由安排配对方式及每场对决的胜负。

定义某一轮的观赏值为”该轮开始时仍在赛场的所有队伍的能力值总和”。当场上只剩 $1$ 支队伍时比赛结束(该轮不计入观赏值)。

请你安排策略使得所有轮次观赏值之和最大,输出这个最大值。

每个测试文件包含 $T$ 组数据。每组第一行输入 $n$,第二行输入 $n$ 个整数 $a_1, a_2, \ldots, a_n$。

样例

输入

3
1
5
2
3 7
4
1 5 2 8

输出

0
10
29

思路分析

第一步:分析淘汰节奏

每轮有 $m$ 支队伍参赛,结束后存活 $\lceil m/2 \rceil$ 支。无论 $m$ 是奇是偶,下一轮队伍数都是 $\lceil m/2 \rceil$。

第二步:贪心策略——让强队留得更久

每轮观赏值 = 当前存活队伍的能力总和。要让总和最大,就应该让能力值大的队伍尽可能多出现在每一轮中。

关键洞察:我们可以自由安排配对和胜负,所以每轮可以让任意队伍被淘汰。最优策略是每轮淘汰能力值最小的那些队伍,即第 $k$ 轮的存活集合恰好是能力值前 $\lceil m/2 \rceil$ 大的队伍。

第三步:前缀和累加

将能力值降序排序,令 $\text{cum}[k]$ 表示前 $k$ 大能力之和。每轮存活 $m$ 支队伍时该轮观赏值为 $\text{cum}[m]$,然后 $m \leftarrow \lceil m/2 \rceil$,循环直到 $m = 1$。

验证样例:$n=4$,排序后 $[8, 5, 2, 1]$,$\text{cum} = [0, 8, 13, 15, 16]$。

  • $m=4$:观赏值 $\text{cum}[4] = 16$,$m \leftarrow 2$
  • $m=2$:观赏值 $\text{cum}[2] = 13$,$m \leftarrow 1$
  • 总和 $= 16 + 13 = 29$

题解代码

import sys
input = sys.stdin.readline

def solve():
    T = int(input())
    for _ in range(T):
        n = int(input())
        a = list(map(int, input().split()))
        a.sort(reverse=True)
        # cum[k] = 能力值前 k 大之和
        cum = [0] * (n + 1)
        for i in range(n):
            cum[i + 1] = cum[i] + a[i]
        ans = 0
        m = n
        while m > 1:
            ans += cum[m]
            m = (m + 1) // 2
        print(ans)

solve()

复杂度分析

时间复杂度:$O(n \log n)$,瓶颈在排序,累加只需 $O(\log n)$ 轮 空间复杂度:$O(n)$,前缀和数组


第三题:lcm计数

题目描述

给定两个正整数 $x$ 与 $a$,计算有多少个正整数 $b$ 满足 $\text{lcm}(a, b) = x$。

每个测试文件包含 $T$ 组数据,每组输入两个正整数 $x, a$。

样例

输入

5
6 2
12 3
12 4
12 6
12 7

输出

2
2
3
2
0

思路分析

第一步:前提条件判断

$\text{lcm}(a, b)$ 一定是 $a$ 的倍数。所以如果 $a \nmid x$($x$ 不能被 $a$ 整除),则不存在满足条件的 $b$,答案为 $0$。

第二步:将问题转化为质因数指数约束

将 $x$ 分解为质因数形式 $x = p_1^{e_1} \cdot p_2^{e_2} \cdots p_k^{e_k}$。设 $a$ 在 $p_i$ 上的指数为 $\alpha_i$,$b$ 在 $p_i$ 上的指数为 $\beta_i$。

由 $\text{lcm}$ 的定义:$\text{lcm}(a, b)$ 在每个质数 $p_i$ 上的指数等于 $\max(\alpha_i, \beta_i)$。

条件 $\text{lcm}(a, b) = x$ 等价于对每个质数 $p_i$:$\max(\alpha_i, \beta_i) = e_i$。

第三步:逐质数分析自由度

令 $d = x / a$。对每个质因数 $p_i$ 分两种情况:

  • 若 $\alpha_i = e_i$(即 $p_i \nmid d$):$a$ 已经把指数顶到了 $e_i$,所以 $\beta_i$ 可以在 $[0, e_i]$ 中任取,共 $e_i + 1$ 种选择
  • 若 $\alpha_i < e_i$(即 $p_i \mid d$):必须靠 $b$ 把指数顶上去,$\beta_i$ 被锁死为 $e_i$,只有 $1$ 种选择

第四步:乘法原理

各质数的选择互相独立,总方案数 = 各质数贡献的乘积。

验证样例:$x = 12 = 2^2 \times 3^1$,$a = 4 = 2^2 \times 3^0$,$d = 3$。

  • $p = 2$:$e = 2$,$d \% 2 \neq 0$,贡献 $2 + 1 = 3$
  • $p = 3$:$e = 1$,$d \% 3 = 0$,贡献 $1$(锁死)
  • 答案 $= 3 \times 1 = 3$,对应 $b \in {4, 12, 36}$… 等等,让我重新验证:$b$ 满足 $\text{lcm}(4, b) = 12$,所以 $b \in {3, 6, 12}$,确实是 $3$ 个

题解代码

import sys
input = sys.stdin.readline

def count_b(x, a):
    if x % a != 0:
        return 0
    d = x // a
    res = 1
    temp = x
    p = 2
    while p * p <= temp:
        if temp % p == 0:
            e = 0
            while temp % p == 0:
                temp //= p
                e += 1
            # p 不整除 d 说明 a 已顶满该质数,b 有 e+1 种选择
            if d % p != 0:
                res *= e + 1
        else:
            p += 1
    # 剩余大质因数
    if temp > 1:
        if d % temp != 0:
            res *= 2
    return res

def solve():
    T = int(input())
    for _ in range(T):
        x, a = map(int, input().split())
        print(count_b(x, a))

solve()

复杂度分析

时间复杂度:$O(\sqrt{x})$,试除法分解质因数 空间复杂度:$O(1)$,只用若干计数变量


小结

  • 第一题是经典的模拟题,关键在于区分两种违规(重叠落子 vs 胜后继续)并正确处理优先级,难度不大但细节多
  • 第二题的核心洞察是”自由安排配对”意味着每轮可以选择淘汰谁,贪心地保留能力值最大的队伍,排序+前缀和即可高效求解
  • 第三题是数论经典问题,把 $\text{lcm}$ 条件拆解到每个质因数上的指数约束后,问题变为简单的乘法原理计数