大厂真题

华为AI岗 2026-09-09

内容边界:可恢复 10 道选择题和 2 道编程题。CDN 题的中心更新同代价、空簇规则未完整给出,本文明确列出参考约定,不能据此声称唯一标准解。

本场考试概述

考试时间:2026-9-9 考试岗位:AI岗 难度评级:中等偏难

考点分析

  • 选择题:材料声称 20 道,实际可恢复 10 道;未公开的另外 10 道不补写。
  • 第一题 动态KV缓存稀疏优化:排序贪心(难度中等)
  • 第二题 CDN分发服务器选址:k-medoids 迭代(难度困难)

建议策略

  • 选择题的重心压在数学上,线性代数一门就占了近三分之一,转置、可逆、线性相关、迭代法这几个定义点务必背熟,属于必拿分
  • 第一题看着吓人,实际规则题面已经写死,把「全局排序 + 三级优先级」翻译成一个排序键就结束了,不要真的去逐轮挑最大值
  • 第二题是流程照做题,算法骨架题面给全了,分数全落在五条规则有没有一条不落地实现,尤其是两处同分怎么断

选择题(可恢复 10 道)

单选题

1、假设 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 均为 数学公式(保留原始 SVG) 的方阵。在矩阵运算中,下列等式恒成立的是:

A. 数学公式(保留原始 SVG) B. 数学公式(保留原始 SVG) C. 数学公式(保留原始 SVG) D. 数学公式(保留原始 SVG)

答案:A 难度:简单 考点:数学—线性代数 解释:矩阵乘法满足结合律但不满足交换律,数学公式(保留原始 SVG) 只用到了结合律,因此对任意方阵恒成立。B 错在转置要反序,正确的是 数学公式(保留原始 SVG),只有 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 相等时才凑巧成立。C 展开为 数学公式(保留原始 SVG),要合并成 数学公式(保留原始 SVG) 必须 数学公式(保留原始 SVG),一般矩阵不可交换。D 展开为 数学公式(保留原始 SVG),同样需要 数学公式(保留原始 SVG) 才能消去中间两项。三个错误选项的病根是同一个:把实数的交换律搬到了矩阵上。

2、矩阵 数学公式(保留原始 SVG) 的协方差矩阵维度是?

A. 数学公式(保留原始 SVG) B. 数学公式(保留原始 SVG) C. 数学公式(保留原始 SVG) D. 数学公式(保留原始 SVG)

答案:A 难度:简单 考点:数学—线性代数 解释:把 数学公式(保留原始 SVG) 看成 数学公式(保留原始 SVG) 个样本、每个样本 数学公式(保留原始 SVG) 维特征,协方差矩阵刻画的是特征与特征之间的相关性,第 数学公式(保留原始 SVG) 个元素为特征 数学公式(保留原始 SVG) 与特征 数学公式(保留原始 SVG) 的协方差,因此阶数由特征数 数学公式(保留原始 SVG) 决定。按定义 数学公式(保留原始 SVG),维度为 数学公式(保留原始 SVG)。B 是把样本数当成了阶数,对应的 数学公式(保留原始 SVG) 是样本间的 Gram 矩阵而非协方差矩阵;C、D 都不是方阵,而协方差矩阵必为对称方阵。

3、假设单头注意力模型有 数学公式(保留原始 SVG) 层,隐藏层维度为 数学公式(保留原始 SVG),当前的 Context 长度为 数学公式(保留原始 SVG),采用 FP16 存储,单个 Token 的 KV Cache 显存占用公式为?

A. 数学公式(保留原始 SVG) 字节 B. 数学公式(保留原始 SVG) 字节 C. 数学公式(保留原始 SVG) 字节 D. 数学公式(保留原始 SVG) 字节

答案:B 难度:中等 考点:大模型—微调与推理部署 解释:单个 Token 在每一层都要缓存 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 两个向量,每个向量 数学公式(保留原始 SVG) 个元素,故每层 数学公式(保留原始 SVG) 个数;共 数学公式(保留原始 SVG) 层得 数学公式(保留原始 SVG) 个数;FP16 每个数 数学公式(保留原始 SVG) 字节,总计 数学公式(保留原始 SVG) 字节。A 漏掉了层数 数学公式(保留原始 SVG),只算了一层。C 的系数 数学公式(保留原始 SVG) 相当于每层缓存四个矩阵,多算了 数学公式(保留原始 SVG) 和输出,而 数学公式(保留原始 SVG) 用完即弃、不进 Cache。D 混淆了统计口径:题目问的是”单个 Token”的占用,乘上 数学公式(保留原始 SVG) 得到的是整个 Context 的总占用,且它还漏了 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 的因子 数学公式(保留原始 SVG)

4、逻辑回归使用交叉熵损失而非均方误差(MSE)的核心原因是:

A. 交叉熵损失只适用于二分类问题 B. 交叉熵损失的计算速度更快 C. MSE 无法处理概率值输出 D. MSE 用于逻辑回归时目标函数非凸,易陷入局部最优;交叉熵损失是凸函数

答案:D 难度:简单 考点:机器学习—逻辑回归 解释:把 Sigmoid 套进平方误差后,数学公式(保留原始 SVG) 关于 数学公式(保留原始 SVG) 不是凸函数,梯度下降可能停在局部极小;而交叉熵与 Sigmoid 组合后关于 数学公式(保留原始 SVG) 是凸的,但不一定严格凸;最优解未必唯一,线性可分时甚至可能不存在有限参数最优解。还有一个连带好处:交叉熵的梯度为 数学公式(保留原始 SVG),Sigmoid 的导数被约掉了,预测越离谱梯度越大;MSE 的梯度带有 数学公式(保留原始 SVG) 因子,在饱和区趋近于 数学公式(保留原始 SVG),会导致学习极慢。A 错在交叉熵天然支持多分类(Softmax 交叉熵)。B 错在两者计算量相当,速度不是选择依据。C 错在 MSE 在数值上完全可以接受概率输出,问题出在优化性质而非能否计算。

5、向量组线性相关是指

A. 向量组中至少有一个零向量 B. 存在一组不全为零的系数使得线性组合为零向量 C. 任意向量都可由其余向量线性表示 D. 向量的个数大于维数

答案:B 难度:入门 考点:数学—线性代数 解释:这就是线性相关的定义:存在不全为零的 数学公式(保留原始 SVG) 使 数学公式(保留原始 SVG)。A 是充分不必要条件,含零向量必线性相关,但 数学公式(保留原始 SVG) 无零向量也相关。C 把”至少有一个”错写成”任意”,如 数学公式(保留原始 SVG) 相关,但 数学公式(保留原始 SVG) 无法由前两个表示。D 同样只是充分条件,数学公式(保留原始 SVG) 维空间中超过 数学公式(保留原始 SVG) 个向量必相关,但两个共线向量在二维里个数不超过维数却依然相关。

6、在使用 K-Means 时,常用” 手肘法 “(Elbow Method)来确定最佳 K 值。该方法观察的是哪个指标随 K 值变化的趋势?

A. Davies-Bouldin 指数 B. 调整兰德指数(ARI) C. 误差平方和(SSE / Inertia) D. 轮廓系数 (Silhouette Score)

答案:C 难度:简单 考点:机器学习—聚类 解释:手肘法画的是 SSE(簇内样本到各自簇心的平方距离和,sklearn 里叫 Inertia)随 数学公式(保留原始 SVG) 的下降曲线。数学公式(保留原始 SVG) 增大 SSE 必然单调下降,当 数学公式(保留原始 SVG) 越过真实簇数后下降速度骤缓,曲线出现形如手肘的拐点,该拐点即取值。A 和 D 都是聚类效果的内部评价指标,选 数学公式(保留原始 SVG) 时取极值而非看拐点,不叫手肘法。B 是需要真实标签的外部指标,而 K-Means 场景通常无标签可用。

7、注意力机制中, 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 的乘积维度是?

A. 数学公式(保留原始 SVG) B. 数学公式(保留原始 SVG) C. 数学公式(保留原始 SVG) D. 数学公式(保留原始 SVG)

答案:A 难度:入门 考点:深度学习—Transformer 解释:批量矩阵乘法只在后两维做矩阵乘,第一维 数学公式(保留原始 SVG) 作为批维度对齐后保留。后两维按 数学公式(保留原始 SVG) 相乘,内维 数学公式(保留原始 SVG) 相消,得 数学公式(保留原始 SVG),故结果为 数学公式(保留原始 SVG)。这个矩阵正是注意力分数矩阵,第 数学公式(保留原始 SVG) 项表示第 数学公式(保留原始 SVG) 个 query 对第 数学公式(保留原始 SVG) 个 key 的相关度,行数必须等于 query 个数 数学公式(保留原始 SVG)。B 把两维写反了,对应的是 数学公式(保留原始 SVG)。C 误把被消掉的内维 数学公式(保留原始 SVG) 当成了结果维。D 是 数学公式(保留原始 SVG) 自身的形状,是没做乘法的结果。

8、矩阵 数学公式(保留原始 SVG),其中 数学公式(保留原始 SVG) 是下三角部分,数学公式(保留原始 SVG) 是对角部分,数学公式(保留原始 SVG) 是上三角部分。那么 数学公式(保留原始 SVG) 的 Gauss-Seidel 迭代法的迭代矩阵为:

A. 数学公式(保留原始 SVG) B. 数学公式(保留原始 SVG) C. 数学公式(保留原始 SVG) D. 数学公式(保留原始 SVG)

答案:D 难度:中等 考点:数学—线性代数 解释:Gauss-Seidel 的思想是算新分量时立刻用上本轮已更新的值,即把 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 一起划到左端隐式求解:数学公式(保留原始 SVG),两边左乘 数学公式(保留原始 SVG)数学公式(保留原始 SVG),迭代矩阵即 数学公式(保留原始 SVG)。A 是 Jacobi 迭代的迭代矩阵,它只把 数学公式(保留原始 SVG) 留在左端、整轮都用上一轮的旧值。B 缺负号且丢了 数学公式(保留原始 SVG),不对应任何标准格式。C 把 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 的角色对调,那是按逆序更新(后向 Gauss-Seidel)的形式,与题目给定的下三角、上三角约定不符。

9、已知矩阵 数学公式(保留原始 SVG),则 数学公式(保留原始 SVG) 的转置矩阵 数学公式(保留原始 SVG)

A. 数学公式(保留原始 SVG) B. 数学公式(保留原始 SVG) C. 数学公式(保留原始 SVG) D. 数学公式(保留原始 SVG)

答案:D 难度:入门 考点:数学—线性代数 解释:转置就是行列互换,数学公式(保留原始 SVG),主对角线元素 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 不动,副对角线的 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 交换位置,得 数学公式(保留原始 SVG)。A 是把矩阵旋转 数学公式(保留原始 SVG) 的结果。B 是原矩阵未做任何操作,只有对称矩阵才满足 数学公式(保留原始 SVG)。C 只换了副对角线上的一个位置,并非合法的转置。

10、在预训练过程中,关于评估(Evaluation)和训练终止判断,以下做法最合理的是?

A. 定期在多样化的下游任务(如 HellaSwag、ARC、代码补全)上评估零样本(Zero-shot)性能,综合判断模型能力饱和情况 B. 当验证集 Loss 开始上升时立即停止,以防止过拟合 C. 仅在训练结束时评估,以节省计算资源,中间评估会显著拖慢训练进度 D. 以训练 Loss 作为唯一指标,当 Loss 不再下降时立即停止训练

答案:A 难度:简单 考点:大模型—评测与工程治理 解释:预训练关心的是通用能力,而 Loss 只是代理指标,与下游表现并非严格同步,常见现象是 Loss 已趋平但下游任务分数仍在爬升。因此工业界用一组多样化基准定期做零样本评估,综合判断能力是否饱和。B 错在大规模预训练通常单轮遍历海量语料,几乎不出现传统意义的过拟合,验证 Loss 的短期抖动更可能来自数据分布切换或学习率调度,据此立即停机会误杀。C 错在没有中间评估就无法及时发现训练发散或数据污染,等训练结束再看等于把整轮算力压在一次赌注上;且评估开销相对训练极小。D 错在训练 Loss 受学习率调度影响很大,且它衡量的是拟合语料的程度而非通用能力,单指标终止会过早停在能力尚未饱和的位置。


第 1 题:动态KV缓存稀疏优化

题目描述

在大型语言模型(LLM)的实时推理场景中(如长文档生成、无限对话系统),KV 缓存会随序列增长消耗大量显存。为解决显存瓶颈,需要对 KV 缓存进行稀疏化处理:在全局显存预算约束下,为每层每个注意力头选择重要性较高的 token 保留其 KV 缓存,且每层可保留不同数量的 token。

约束要求如下:

  1. 必须保留当前正在生成的 token(位置索引 数学公式(保留原始 SVG))的 KV 缓存。
  2. 其他剩余位置的 KV,按注意力分数从高到低全局排序,优先保留注意力分数高的 token。
  3. 当注意力分数相同时,按以下优先级选择:层索引 数学公式(保留原始 SVG) 从低到高排序,头索引 数学公式(保留原始 SVG) 从低到高排序,token 索引 数学公式(保留原始 SVG) 从低到高排序。例如 数学公式(保留原始 SVG)数学公式(保留原始 SVG),则优先保留 数学公式(保留原始 SVG) 内的 token。
  4. 总保留 token 数不超过显存预算 数学公式(保留原始 SVG),本题中显存预算保证每层每头至少保留 数学公式(保留原始 SVG) 个 token。

核心解题步骤如下:

  1. 计算必须保留的 token 数 数学公式(保留原始 SVG)(即每层每头位置 数学公式(保留原始 SVG) 各一个),从预算中减去必须保留的 token 数:数学公式(保留原始 SVG)
  2. 对除位置 数学公式(保留原始 SVG) 之外的所有注意力分数进行全局从高到低排序,取注意力分数前 数学公式(保留原始 SVG) 的数值,记录对应的层索引 数学公式(保留原始 SVG)、头索引 数学公式(保留原始 SVG)、token 索引 数学公式(保留原始 SVG)
  3. 如遇到注意力分数相同且超出显存预算的情况,按照约束要求 数学公式(保留原始 SVG) 的规则选择。
  4. 输出符合要求的每层每头最终保留的 token 索引 数学公式(保留原始 SVG)

输入描述

第一行包含四个整数 数学公式(保留原始 SVG),其中 数学公式(保留原始 SVG) 为层数、数学公式(保留原始 SVG) 为头数、数学公式(保留原始 SVG) 为序列长度、数学公式(保留原始 SVG) 为显存预算。

随后 数学公式(保留原始 SVG) 行:每行包含 数学公式(保留原始 SVG) 个浮点数(空格分隔),表示该层所有头的注意力分数,每头连续 数学公式(保留原始 SVG) 个浮点数,按照头索引顺序排列,每个注意力分数满足 数学公式(保留原始 SVG) 且最多保留 数学公式(保留原始 SVG) 位小数。

最后一行:数学公式(保留原始 SVG),表示当前生成位置索引。

输出描述

输出 数学公式(保留原始 SVG) 行,每行表示一层,包含 数学公式(保留原始 SVG) 个字符串,字符串之间用空格分隔。

每个字符串表示该层该头保留的 token 索引 数学公式(保留原始 SVG),索引按照升序排列,以逗号分隔。

样例1

输入

2 2 3 7
0.1 0.2 0.1 0.3 0.3 0.3
0.3 0.4 0.6 0.3 0.5 0.6
1

输出

1 0,1
1,2 1,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)。因此第 数学公式(保留原始 SVG) 层两个头分别保留 数学公式(保留原始 SVG)数学公式(保留原始 SVG),第 数学公式(保留原始 SVG) 层两个头都保留 数学公式(保留原始 SVG)

样例2

输入

2 1 3 3
0.5 0.1 0.5
0.5 0.2 0.4
1

输出

0,1
1

样例解释

必须保留的位置 数学公式(保留原始 SVG) 占掉 数学公式(保留原始 SVG) 个预算,剩余预算为 数学公式(保留原始 SVG)

候选中分数为 数学公式(保留原始 SVG) 的有三个,分别位于第 数学公式(保留原始 SVG) 层第 数学公式(保留原始 SVG) 头的位置 数学公式(保留原始 SVG)、第 数学公式(保留原始 SVG) 层第 数学公式(保留原始 SVG) 头的位置 数学公式(保留原始 SVG)、第 数学公式(保留原始 SVG) 层第 数学公式(保留原始 SVG) 头的位置 数学公式(保留原始 SVG)。按层索引、头索引、token 索引依次比较,第 数学公式(保留原始 SVG) 层第 数学公式(保留原始 SVG) 头的位置 数学公式(保留原始 SVG) 排在最前,唯一的剩余预算给了它。

题解:排序贪心

思路分析

每层每个注意力头都有 数学公式(保留原始 SVG) 个 token 的注意力分数,全局只允许保留 数学公式(保留原始 SVG) 份 KV 缓存,其中每层每头当前生成位置 数学公式(保留原始 SVG) 的那份必须保留。问每层每头最终留下哪些 token 索引。

这是一道规则已经写死、难点在读懂规则的排序贪心题:要看清剩余名额是跨层跨头统一竞争的,以及同分时那条三级优先级怎么落到比较器上。

算法实现

先看名额怎么分。位置 数学公式(保留原始 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),分数矩阵与候选池各占这个量级。

题解代码

import sys
input = sys.stdin.readline

def solve(L, H, N, M, scores, C):
    """在显存预算 M 之内,算出每层每头最终保留的 token 索引。"""
    kept = [[[C] for _ in range(H)] for _ in range(L)]
    remaining = M - L * H

    cands = []
    for l in range(L):
        for h in range(H):
            row = scores[l][h]
            for t in range(N):
                if t == C:
                    continue
                cands.append((-row[t], l, h, t))
    cands.sort()

    for _, l, h, t in cands[:max(remaining, 0)]:
        kept[l][h].append(t)

    for l in range(L):
        for h in range(H):
            kept[l][h].sort()
    return kept

L, H, N, M = map(int, input().split())

scores = []
for _ in range(L):
    row = list(map(float, input().split()))
    scores.append([row[h * N:(h + 1) * N] for h in range(H)])
C = int(input())

kept = solve(L, H, N, M, scores, C)
for l in range(L):
    print(' '.join(','.join(map(str, kept[l][h])) for h in range(H)))

正确性说明

先保留所有强制位置是可行解的必要条件。余下候选的比较键是题面规定的全序,取其前 remaining 个恰好执行规则;最终只对各头索引重排,不改变被选集合。

易错点与边界

全局预算不是每头均分;强制位置不能在候选池再次选中;分数相同时依次比较层、头、位置。

第 2 题:CDN分发服务器选址

题目描述

CDN 分发服务器是内容运营平台的重要组成部分,按业务设计,用户一般从距离最近的 CDN 服务器中下载内容。CDN 选址需要综合考虑覆盖的用户数量、距离等,从而使全部用户离 CDN 服务器的总距离相对较短。

现有 数学公式(保留原始 SVG) 个城市,已知各城市坐标和用户数,以及预算可支持的 CDN 数目 数学公式(保留原始 SVG),请使用 kmeans 算法求解 CDN 的选址坐标和覆盖用户数量。

注意,请按照如下要求计算:

  1. 初始阶段默认地图中无 CDN。
  2. CDN 选址必须在城市内。
  3. 迭代初始 CDN 选址为前 数学公式(保留原始 SVG) 个城市。
  4. 当 CDN 不再变化时停止迭代。
  5. 如果出现多个与用户距离最短的 CDN,则选择序号靠前的 CDN。

单个用户到 CDN 的距离计算公式为:

数学公式(保留原始 SVG)

其中城市坐标为 数学公式(保留原始 SVG),CDN 坐标为 数学公式(保留原始 SVG)

全部用户总距离为:

数学公式(保留原始 SVG)

其中城市 数学公式(保留原始 SVG) 的坐标是 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 是距离城市 数学公式(保留原始 SVG) 距离最小的 CDN 的坐标,数学公式(保留原始 SVG) 是城市 数学公式(保留原始 SVG) 的用户数量。

输入描述

数学公式(保留原始 SVG) 行为参数 数学公式(保留原始 SVG),代表地图内的城市数目,数学公式(保留原始 SVG) 为正整数。

数学公式(保留原始 SVG) 行至第 数学公式(保留原始 SVG) 行,每一行格式为 数学公式(保留原始 SVG),其中 数学公式(保留原始 SVG) 代表该城市横纵坐标,数学公式(保留原始 SVG) 代表该城市用户数,单位为万人,数学公式(保留原始 SVG) 均为 数学公式(保留原始 SVG) 位小数的浮点数,取值范围 数学公式(保留原始 SVG)

数学公式(保留原始 SVG) 行为参数 数学公式(保留原始 SVG),代表当前预算可支持的 CDN 数目,数学公式(保留原始 SVG) 为正整数。

输出描述

数学公式(保留原始 SVG) 行至第 数学公式(保留原始 SVG) 行,每一行格式为 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 代表该 CDN 的横纵坐标,数学公式(保留原始 SVG) 代表该 CDN 覆盖的用户数,单位为万人,数学公式(保留原始 SVG) 均为 数学公式(保留原始 SVG) 位小数的浮点数。

CDN 的顺序按照 数学公式(保留原始 SVG) 从小到大排序,若两城市 数学公式(保留原始 SVG) 相同,则按照 数学公式(保留原始 SVG) 从小到大排序。

样例1

输入

3
0.00,0.00,2.00
1.00,1.00,3.00
2.00,2.00,5.00
1

输出

1.00,1.00,10.00

样例解释

三个城市为一簇,簇内选择一个最佳城市,使簇内所有用户到该城市的距离总和最小。

样例2

输入

4
0.00,0.00,1.00
1.00,1.00,2.00
3.00,3.00,1.00
4.00,4.00,2.00
2

输出

1.00,1.00,3.00
4.00,4.00,3.00

样例解释

四个城市坐标分别为 数学公式(保留原始 SVG)

循环开始时,初始 CDN 设在 数学公式(保留原始 SVG)数学公式(保留原始 SVG) 两个点,通过迭代明确其余两点最近的 CDN 在 数学公式(保留原始 SVG),四个点分裂为两个簇,簇内重新计算最佳 CDN。挪动 CDN 坐标后,再次重新分簇,直到平衡。最终 CDN 坐标、人员分组如上输出。

题解:k-medoids 迭代

思路分析

数学公式(保留原始 SVG) 个城市里挑 数学公式(保留原始 SVG) 个放 CDN,每个城市的用户都就近接入,按题面规定的迭代过程反复”分簇、挪址”直到 CDN 不再变化,输出最终各 CDN 的坐标与它覆盖的用户数。

这是一道流程照做题:算法骨架题面已经给全,难点不在想到什么,而在把五条规则一条不落地落进代码,尤其是两处同分怎么断。

算法实现

先看它与课本 kmeans 的差别。标准 kmeans 的簇心取簇内坐标均值,可以落在任意位置;本题第二条规则要求 CDN 必须建在城市里,所以挪址时只能从簇内已有的城市中挑一个,这就是 k-medoids。

挪址挑谁,由题面的 数学公式(保留原始 SVG) 式决定。对簇 数学公式(保留原始 SVG),选城市 数学公式(保留原始 SVG) 的代价为

数学公式(保留原始 SVG)

数学公式(保留原始 SVG) 最小的那个 数学公式(保留原始 SVG)。距离必须按用户数 数学公式(保留原始 SVG) 加权,漏掉权重在样例二就会对不上。

算法实现上分三步:

第一步,取前 数学公式(保留原始 SVG) 个城市作为初始 CDN,序号 数学公式(保留原始 SVG) 的 CDN 记作 数学公式(保留原始 SVG)

第二步,分簇。每个城市扫一遍 数学公式(保留原始 SVG) 个 CDN,归给距离最小的那个。

第三步,挪址。每个簇内枚举候选城市 数学公式(保留原始 SVG),取 数学公式(保留原始 SVG) 最小者作为新的 数学公式(保留原始 SVG)。新旧 数学公式(保留原始 SVG) 完全一致就停止,否则回到第二步。

两处同分都按”序号靠前”断:分簇时等距取 CDN 序号小的,挪址时代价相等取城市序号小的。实现上只要坚持”严格更优才更新”,先遍历到的那个自然胜出。

数值计算用 1e-9 容差处理样例中的理论同代价;这一容差是工程约定,并未证明所有合法输入的非零代价间隙都大于它。

精确计算下目标值不增,但仅凭“有限状态 + 单调不增”不能排除同值循环。实现按确定性规则更新,检测重复中心配置;若未达到固定点而出现循环,则显式报错,不把旧分簇当成收敛答案。浮点容差不构成数学上的全局收敛证明。

复杂度分析

时间复杂度:O(Tp²+q log q),T 为到固定点或重复配置前的轮数,不预设为常数。

空间复杂度:O(p+Tq),包含城市、分簇及用于循环检测的中心历史。

题解代码

import sys
input = sys.stdin.readline

from math import sqrt

EPS = 1e-9

def dist(a, b):
    """两座城市之间的欧氏距离,城市用 (x, y, u) 三元组表示。"""
    return sqrt((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2)

def assign(cs, centers):
    """分簇:每个城市归给距离最短的 CDN,同距时保留序号靠前的那个。"""
    cluster = [[] for _ in centers]
    for i in range(len(cs)):
        best_j = 0
        best_d = dist(cs[i], cs[centers[0]])
        for j in range(1, len(centers)):
            d = dist(cs[i], cs[centers[j]])
            if d < best_d - EPS:
                best_d = d
                best_j = j
        cluster[best_j].append(i)
    return cluster

def relocate(cs, centers, cluster):
    """挪址:簇内换一个城市当 CDN,使簇内加权距离和最小。"""
    nxt = list(centers)
    for j in range(len(centers)):
        mem = cluster[j]
        if not mem:
            continue
        best_c = -1
        best_cost = 0.0
        for c in mem:
            cost = 0.0
            for i in mem:
                cost += cs[i][2] * dist(cs[i], cs[c])
            if best_c < 0 or cost < best_cost - EPS:
                best_cost = cost
                best_c = c
        nxt[j] = best_c
    return nxt

def solve(cs, q):
    """迭代到 CDN 不再变化,返回最终选址与对应的分簇。"""
    centers = list(range(q))
    cluster = [[] for _ in range(q)]
    seen = set()
    while tuple(centers) not in seen:
        seen.add(tuple(centers))
        cluster = assign(cs, centers)
        nxt = relocate(cs, centers, cluster)
        if nxt == centers:
            return centers, cluster
        centers = nxt
    raise ValueError("CDN 中心出现循环,题面未规定此时的输出")

p = int(input())
cs = []
for _ in range(p):
    a, b, c = input().split(',')
    cs.append((float(a), float(b), float(c)))
q = int(input())

centers, cluster = solve(cs, q)

rows = []
for j in range(q):
    cov = 0.0
    for i in cluster[j]:
        cov += cs[i][2]
    rows.append((cs[centers[j]][0], cs[centers[j]][1], cov))
rows.sort(key=lambda r: (r[0], r[1]))
for r in rows:
    print("%.2f,%.2f,%.2f" % r)

正确性说明

分配步骤逐城市选择最近中心,固定中心时最小化加权距离;更新步骤枚举簇内所有城市,固定分簇时得到最小加权距离的候选。该流程是局部迭代,不保证全局最优。本文的同代价和空簇规则属于明确列出的实现约定。

易错点与边界

题面称 kmeans,但要求城市内选址且优化欧氏距离加权和,本文按样例解释采用 weighted k-medoids。题面仅明确等距分配选择较早 CDN,没有完整规定中心更新同代价、空簇与数值容差:本文约定更新同代价取输入序号较早城市、空簇保持原址、使用 1e-9 容差。这是与给定样例一致的参考实现,不宣称唯一标准答案或全局最优。坐标约束写为正数,但样例含 0.00,程序兼容零坐标。

小结

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