
1. 这道题到底在考什么三角形存在性判定背后的推理逻辑很多刷题练手的朋友第一次碰到“T12//判断三角形”会觉得这也太简单了——输入三条边判断能不能组成三角形能的话再说是等边、等腰还是直角。说句实话我当年第一次做这道题时也是这个想法几秒钟写完代码提交然后就不管了。后来在公司里带新人发现不少有工作经验的人也会在类似的逻辑上栽跟头我才意识到这道题远不是“会写if”就能过关的。这里先把题目复述清楚输入三个正数表示三条边长先判断能否构成三角形如果不能输出对应的提示如果能再进一步判断三角形类型通常要求区分为普通三角形、等腰三角形、等边三角形、直角三角形有些版本还要求输出“等腰直角三角形”这种叠加类型。判断三角形的基本数学依据是三角形存在性定理任意两边之和大于第三边。听起来很直观但“任意”这两个字恰恰是新手的第一个分水岭。我见过不少人只写了a b c这一个条件理由是自己默认 a、b 是短边c 是长边。可问题在于题目并没有保证输入的顺序——万一输入的第一个数就是最长边呢条件就变成判断两边长度是否大于一个较小的数结果当然是错的。所以严谨的写法必须把三组不等式都列出来a b c、a c b、b c a三者同时成立才算三角形。但先别急着堆三行 if还有一个更高效的等价思路先把三条边按从小到大排序得到x y z。因为z是最大边x z y和y z x天然成立唯一需要检查的是最小两边之和大于最大边也就是x y z。这样一来原来三条不等式就压缩成一条判断逻辑上少了一半的出错可能。这个“先排序再判定”的小技巧本质上是一种降维在后续很多几何题里都能复用。至于判断类型优先级也要先想清楚。等边三角形显然满足等腰条件但如果测试数据里出现(2, 2, 2)这样的输入你的代码是输出“等边”还是“等腰”绝大多数题目的约定是先判等边再判等腰把等腰直角三角形作为额外的组合情形单独处理。所以类型判断的顺序会直接影响输出拿到题先看题目约定别想当然。2. 三种解法模板从最直觉的 if 串联到排序后统一判断如果只是追求“能通过”那解法一最直接。假设三条边保存在变量 a、b、c 里类型判断可以写成若干 if/else 的串联。用 C 语言写大致是这个样子if (a b c a c b b c a) { if (a b b c) { printf(Equilateral\n); } else if (a b || b c || a c) { printf(Isosceles\n); } else if (a*a b*b c*c || a*a c*c b*b || b*b c*c a*a) { printf(Right\n); } else { printf(Normal\n); } } else { printf(No triangle\n); }这种写法最容易理解也最容易在细微处埋雷。比如直角三角形的判断要写三个分支因为直角可能任意一边是斜边。很多人只写了a*a b*b c*c假设 c 是斜边结果一测试(3, 4, 5)的不同排列顺序就露馅了。再比如等腰和直角在(1, 1, sqrt(2))这种情况下会同时成立到底输出哪个这就要靠题目约定来定优先级了。如果想让代码更整洁可以走排序路线。思路是先把输入的三个数排序然后统一处理。我通常会把排序封装成一个单独的函数比如void sort3(double *a, double *b, double *c) { if (*a *b) { double t *a; *a *b; *b t; } if (*b *c) { double t *b; *b *c; *c t; } if (*a *b) { double t *a; *a *b; *b t; } }排序之后x 是最小边z 是最大边。存在性判断变成x y z直角判断只需检查x*x y*y z*z等腰只需看任意相邻两边是否相等。三个 if 就能搞定所有类型代码结构清晰得多。还有一种更“工程化”的写法适合把逻辑封装成函数并复用。比如写一个getTriangleType(a, b, c)内部返回一个枚举值主函数只负责输入和输出。这种写法在刷题时看起来有点“杀鸡用牛刀”但等你去公司写实际项目就会发现逻辑与交互分离、状态用命名常量表达是非常核心的代码组织思想。从一道 T12 开始养成这个习惯后面的代价很小。Python 版本的排序解法尤其简洁完整示例大概是import math def classify(a, b, c): sides sorted([a, b, c]) x, y, z sides[0], sides[1], sides[2] if x 0 or x y z: return No triangle eps 1e-9 is_equ abs(x - y) eps and abs(y - z) eps is_iso abs(x - y) eps or abs(y - z) eps is_right abs(x * x y * y - z * z) eps if is_equ: return Equilateral if is_iso and is_right: return Isosceles Right if is_iso: return Isosceles if is_right: return Right return Normal while True: try: a, b, c map(float, input().split()) except EOFError: break print(classify(a, b, c))两种语言表达的是同一套逻辑排序 → 过滤非法输入 → 存在性判定 → 类型判定。我个人的经验是刷题阶段把两种语言各写一遍对加深理解特别有帮助因为你没法依赖某个语言特性“蒙混过关”逻辑清晰度反而会被迫提升。3. 真正容易翻车的地方浮点误差、零边和输入格式约定这题看起来简单但 OJ 上提交反复出错的往往不是三角形原理没搞懂而是栽在下面这几个细节上。第一个大坑是浮点数比较。很多题目输入的边长是整数用比较无伤大雅可如果数据里有小数或者像sqrt(2)这种无理数直接比较a*a b*b c*c几乎一定会得到 false。计算机里浮点数存储是有精度损失的1.0 / 3 * 3都不一定严格等于1.0更别说平方和了。正确的做法是引入一个很小的误差阈值 epsilondouble eps 1e-6; if (fabs(x*x y*y - z*z) eps) { // 判定为直角 }fabs是取绝对值误差小于 1e-6 就认为左右两边相等。这个阈值怎么选也有讲究太小可能因为浮点误差误判太大又会把明明不等的边判成相等。竞赛里常见 1e-6 到 1e-9具体看题目精度要求。我在实际工程里一般用相对误差但刷题场景用绝对误差就够了。第二个大坑是零边和负数。三角形的边长必须大于零但代码如果只看“两边之和大于第三边”那么输入(0, 1, 1)时0 1 1不成立会判为不是三角形——看起来没问题。可如果排序后只写了x y z没有专门检查x 0那么(0, 1, 1)依然不满足条件与“不是三角形”的预期一致。真正危险的是(-1, 2, 2)这种排序后-1 2 2不成立也能被判掉。所以你可能会觉得“反正存在性判断也会过滤非法输入”但慢着——如果题目要求输出的是“边长必须为正若非法输入则提示非法”而不是“不是三角形”那你的代码就会语义出错。稳妥起见一开始就检查x 0并单独返回非法输入提示后面就不用纠结了。第三个大坑是输入格式的约定。有些题目说输入三个数但没说是整数还是实数有些题目是多组测试数据持续读到文件末尾还有些题目规定读到0 0 0结束。不同约定对应完全不同的读入代码。就拿 C 语言来说读多组数据的标准写法是while (scanf(%lf %lf %lf, a, b, c) ! EOF) { // 处理逻辑 }如果题目要求以 0 0 0 结束就要在处理完三边后先判断三个值是否都为 0是就 break。Python 这边读多组输入则要靠try except EOFError或者sys.stdin.read()统一解析。我见过很多人在这个环节出问题代码逻辑全对但读入循环写错了导致只处理了一组数据就退出。另外还要注意一个不那么明显但真实存在的坑整型溢出。如果题目输入的是很大的整数比如边长达 10 亿那么x * x会达到 10 的 18 次方。C 语言的 int 范围大概在 21 亿左右直接溢出成负数然后直角判断自然出错。所以要么用long long要么在读入时就转成 double两条路都可以提前意识到能省很多调试时间。4. 边界测试用例用一组数据验证你的代码不是“碰巧过了”很多初学者在 OJ 上提交通过之后就觉得自己已经“会了”。但其实通过只是最基础的要求真正的干扰项往往是隐藏的边界条件。把下面这组测试用例跑一遍你会发现自己代码的某些隐藏问题立刻现形。输入 (a, b, c)预期结果这个用例能暴露什么问题(2, 2, 2)Equilateral等边判断是否在等腰之前(3, 3, 5)Isosceles等腰判断的准确性(3, 4, 5)Right直角的排列顺序是否敏感(5, 4, 3)Right输入的边长是否有序(1, 1, 1.414214)Isosceles Right等腰直角三角形组合判断(1, 2, 3)No triangle两边之和等于第三边的情况(1, 1, 3)No triangle两边之和小于第三边(0, 1, 1)Invalid / No triangle零边是否被单独处理(-1, 2, 2)Invalid / No triangle负边是否被过滤(1000000000, 1000000000, 1000000000)Equilateral大数下是否发生整型溢出这里我特别想强调排列组合这件事。很多人会测试(3, 4, 5)但忘了调换顺序后直角判断里如果只写了一个等式代码就会对(5, 3, 4)输出错误结果。三角形存在性判断同理只测一组输入无法证明逻辑完备。我习惯的做法是用三层循环把所有排列都测一遍或者写一段很小的脚本把三个数打乱顺序依次输入确保任意输入顺序下行为一致。等边、等腰、直角、不构成、非法输入这五类分支每一类至少要覆盖一个正例和一个反例。比如等腰的反例是(2, 3, 4)直角的反例是(2, 3, 4)或者(2, 3, 5)它们应该落到 Normal 分支。如果某类分支没有测试到即使代码逻辑是错的也照样能“通过”OJ 的弱测试点但这属于侥幸过关不是真会。我在实际刷题过程中还总结了一个经验拿到一个题的几个小时后再回来重新提交一次。刚写完时往往脑子里还存着“我当时是怎么想的”重看代码容易默认它是对的隔一段时间再读就容易发现变量名混乱、逻辑顺序不清晰、分支覆盖不全的问题。判三角形这种题规模小特别适合做这个回顾练习成本低但收获不小。5. 延伸一步从“判断三角形”到计算几何的思维跳跃如果 T12 只是做完就丢你会错过它更大的价值。三角形存在性判断实际上是计算几何的入门砖很多几何算法都围绕三角形展开掌握了基础判定逻辑后面再看更复杂的算法会轻松不少。一个典型的延伸是“点在三角形内部”的判断。给定一个三角形和任意一点怎么判断这个点是否在三角形内部最常见的做法是利用向量叉积将点与三角形的三个顶点分别连线如果点沿逆时针走都位于每条边的同一侧那就是在三角形内部。这里的核心思想和判断三角形存在性很像——都是把几何问题拆成几个可验证的条件而不是肉眼目测。还有一个思路是面积法如果点 P 在三角形 ABC 内部那么三角形 ABP、BCP、CAP 的面积之和等于三角形 ABC 的面积。用海伦公式或者向量叉积计算三角形面积然后比较差值是否在误差范围内。这种方法写起来直观很适合编程实现但缺点是浮点误差累积大坐标下容易出问题。实际工程更推荐只用叉积的同向法因为它只需要几次乘法和比较。延伸到更复杂的场景三角形判定也会以各种形式出现。图形学里的三角形裁剪、网格简化中的退化面检测都会先判断三个顶点是否构成一个正常的三角形如果三角形的面积接近零或者边长关系不满足就直接跳过避免后续计算除零或产生异常网格。物理引擎里的碰撞检测也经常把凸多边形三角剖分再逐个三角形做相交测试这个链条最底层就是“判断一个三角形是否合法”。这些应用听起来离一道入门题很远但逻辑骨架完全一致。刷 T12 时如果能顺手想清楚“排序后判断”为什么优于“三条不等式分支”你就掌握了做减法——把复杂问题先整理成有序结构再用更少的条件完成等价判断。这个思维模式会反复出现在二分查找、区间合并、数组去重等经典问题里。我建议你把 T12 判题之后的代码改成函数式接口再用 Python 写一个简单的测试脚本随机生成三条边同时用海伦公式计算实际面积来验证“构成三角形”这个结论。这种“随机对拍”的测试方法在后续刷更复杂的几何题时也会非常常用早一点学会后面少走很多弯路。6. 做完题之后简单题里值得长期保留的代码习惯这几个月我偶尔会让团队里的新人做几道“简单题”热热身T12 就在其中。观察下来思路清晰的人写出来的代码结构都很相似输入与判断分离、判断逻辑封装成函数、类型用有意义的单词命名、浮点比较留足误差空间。而那些代码勉强能通过的人往往把逻辑全塞在 main 函数里变量叫 a、b、c类型输出直接用魔法字符串整段代码读起来像一团毛线。我自己的习惯是即使是一道入门题也会按照生产代码的标准来写。比如用枚举或者命名常量表示三角形类型不用裸字符串散落各处入口函数只负责读输入、调用判断函数、输出结果把排序和判定拆成独立函数方便单测。有人觉得这叫“不务正业”做简单题何必这么较真。但我的体会是习惯恰恰是从简单题养成的你在简单题里随手把封装做了等到了复杂项目里想不封装都难反过来如果每个简单题都图省事流水账式地写几年之后依然写流水账。还有一个我现在特别在意的点提交之前先把代码读一遍站在一个没见过这题的陌生人的角度提问——“他为什么要先排序”“这个误差阈值是拍脑袋定的还是有依据的”如果你的回答经得起推敲那这个代码才真正算写明白了。T12 本身不难难的是你能不能借这道题把自己的代码组织能力往上推一层。顺带说一个实用小技巧如果题目允许用标准库排序就不要手写排序函数。C 语言可以用qsortC 直接用std::sortPython 内置sorted三行以内能完成的事没必要自己维护把精力放在真正需要思考的判断逻辑上。手写排序作为练习另当别论但在刷题和工程里优先调用成熟的标准库永远是更稳的选择。最后分享一个从这题里悟出的体会判定类问题最怕的不是分支多而是对输入空间没有完整认识。三条边有大小关系、相等关系、零负关系、精度关系四个维度交叉起来有几十种场景。你能不能在写代码之前先把这些场景列成一张表决定了你的代码是一把梭还是可控逻辑。T12 只是开始后面你会碰到更多更复杂的判定问题这套思路会一直陪着你。