P5717 三角形分类:排序化简与整数平方判据
发布时间:2026/10/27 12:20:33 锦皓数字建站

P5717 这道题在入门题库里只标了个入门难度但它每年都能让一批人栽在评测机上——不是不会写判断而是判断写得全不全、顺序对不对、边界卡没卡住。题目本身很朴素给三条整数边长先回答能不能拼成三角形能拼的话再依次说明它是直角、锐角还是钝角是等腰还是等边。听起来就是几个 if 的事可真动手写排序忘了、不等式写错、平方用 double 比、等边漏输出等腰、输出顺序颠倒这些坑一个接一个。它把新手最容易含糊的几件事——排序化简、三角形不等式的等价变形、用整数平方替代开方、多条件叠加输出——全塞进了二十行代码里。我把它当成一块试金石一个人写判断逻辑是否严谨、对边界是否敏感看这道题就够了。下面我按自己带人做题、自己复盘踩坑的顺序把这道题从题面到代码、从判据到测试、从坑位到延伸完整走一遍代码给 C 和 Python 两个版本测试用例给一套我常用的组合新手可以照着抄有基础的朋友可以重点看判据推导和踩坑那一节。1. 先把题目掰开揉碎三角形分类到底在考什么1.1 输入输出约定先对齐别急着写代码这道题的输入只有一行三个整数代表三条线段的长度题目约定长度是不超过一万的整数。输出的讲究就多了如果三条边拼不成三角形只输出一行Not triangle别的一概不许有如果能拼成则按固定顺序输出若干行——先判断这个三角形的最大角是直角、锐角还是钝角对应Right triangle、Acute triangle、Obtuse triangle三者互斥只会出现一个接着判断它是不是等腰是就输出Isosceles triangle最后判断是不是等边是就输出Equilateral triangle。这里有个非常关键但题面不会明说的规则多个条件同时满足时要全部输出而且顺序不能错。一个等边三角形同时也是等腰三角形所以它必须连续输出Acute triangle、Isosceles triangle、Equilateral triangle三行。很多人第一次提交只输出一行Equilateral triangle评测机直接判错回头翻题面才发现同时满足要全部输出这句话藏在角落里。再强调一遍输出字符串的准确性这是纯粹的白给分也是纯粹的白给扣分点。大小写敏感全部是首字母大写、其余小写单词之间有且只有一个空格行末不要有空格。我把五个答案加一个否定答案列成表写代码的时候直接对着抄别凭记忆敲含义正确输出常见错误写法直角三角形Right triangleright triangle、Right Triangle锐角三角形Acute triangleAcute triangel钝角三角形Obtuse triangleObtuse triangle多余空格等腰三角形Isosceles triangleIsoceles triangle少一个 s等边三角形Equilateral triangleEquilateral无法构成Not trianglenot triangle、Not a triangleIsosceles这个词是拼错重灾区中间是scel不是scel也不是scele我见过不止一个人卡在这上面半小时最后发现是字母顺序敲反了。这种错误排查起来最费劲因为逻辑全对就是字符串不对评测机只告诉你答案错误不告诉你哪里错。1.2 为什么先排序是这道题的第一个关键动作拿到三条边绝大多数人的第一反应是直接写三个判断abc、acb、bca三条都成立才算三角形。这个写法没错但它把后续所有判断都变复杂了。因为在没排序的情况下你永远不知道哪条边最长而最大角的判断必须依赖最长边等腰判断也得老老实实写三组两两比较。排序的价值就在这里把无序变有序把三选一变成唯一确定。排完序后记x≤y≤z那么z一定是最长边z对应的角一定是最大角判断三角形类型只需要盯住这一组x²y²和z²的关系判断等腰只需要看xy或yz因为xz必然蕴含三者全等根本不用单独写判断等边只需要xy yz。代码量直接砍掉一半可读性还上去了。我在实际写题时有个固定习惯只要题目涉及最长边最大角两两比较先花一行sort把数据摆平再动脑子。这一行sort的成本可以忽略收益是把后面所有的分支逻辑都压平了。很多人觉得排序是多余的三行比较而已但等你写到第五个判断条件自己都记不清哪个变量是最大边的时候就知道这行sort值多少钱了。还有个小细节排序之后变量的语义变了。如果你用a、b、c三个变量接收输入排完序之后a不一定还是第一条输入的边。这本身没问题但如果代码里既有原始输入又有排序后的逻辑混着用就会出大问题。我的做法是输入用数组或列表接排序后立刻转成x、y、z三个新变量此后所有判断只用x、y、z从根上避免混用。2. 两个核心判据三角形不等式与勾股定理的正确姿势2.1 三角形不等式三组比较怎么压成一次比较三角形不等式的内容是任意两边之和大于第三边。三边记为a、b、c就要同时满足abc、acb、bca。这三条是且的关系缺一不可。排序成x≤y≤z之后可以证明只需要验证xyz这一条另外两条自动成立。推导过程很直白因为x≤y≤z所以xz ≥ xy而xyz已经成立于是xz z ≥ y即xzy同理yz ≥ xy z ≥ x即yzx。换句话说较小的两条边加起来都能超过最大边那么任何一条边加上最大边必然超过剩下的那条边——毕竟最大边本身都不小于另一条边了。这个化简不是炫技它直接影响代码的正确性。我见过最常见的错误是没排序却只写了一个abc。测试数据1 2 3传进来123不成立判成不能构成三角形侥幸对了但数据3 1 2传进来312成立程序就认为能构成三角形直接错判。顺序一变结果就变这是典型的不排序硬套公式的后果。另一个必须注意的点是取不取等号。三角形不等式是严格大于不是大于等于。1 2 3三条边123拼出来的是一条直线三个顶点共线这不是三角形是退化情形必须输出Not triangle。同理1 1 2、5 5 10都是退化情形。代码里写成xyz就判掉不要写xyz差一个等号就是两类数据的区别。2.2 直角、锐角、钝角的统一判据初中课本讲的是勾股定理直角三角形满足a²b²c²其中c是斜边。但题目还要判断锐角和钝角光靠勾股定理不够得用它的推广形式——余弦定理。设最长边z所对的角为C余弦定理给出cos C (x²y²-z²)/(2xy)。分母2xy是正数边长都为正所以cos C的正负完全由分子x²y²-z²决定。而余弦函数在 0 到 180 度之间单调递减于是分子等于 0cos C 0C 90°直角三角形分子大于 0cos C 0C 90°又因为z是最大边其余两角必然更小所以三个角都小于 90°锐角三角形分子小于 0cos C 0C 90°钝角三角形。为什么只判断最长边对应的角就够因为三角形内角和是 180 度最多只能有一个角大于等于 90 度而大边对大角最长边对应的就是最大角。最大角是锐角其他角不可能跑出锐角范围最大角是钝角这个三角形就是钝角三角形。一次比较解决全部问题这就是排序带来的红利。代码层面我强烈建议用整数平方直接比较不要开方不要算余弦。边长上限一万平方是10⁸量级两个加起来2×10⁸完全在 32 位有符号整数的安全范围内约2.1×10⁹。不过我还是习惯用long long理由很简单以后遇到边长上限更大的题改都不用改省得某天被int溢出背刺一次。2.3 等腰、等边与叠加输出的顺序问题等腰和等边的判定是纯相等比较排序后写起来极短xy || yz就是等腰xy yz就是等边。这里有个容易忽略的点因为已经排序xz必然意味着三者全等所以等腰判断不需要把xz也写进去。当然你写全了也不错只是冗余。真正的坑在输出顺序。题目要求的顺序是先角类型直角/锐角/钝角三选一再等腰再等边。这意味着等边三角形要输出三行而不是一行。很多人的代码长这样先判断是不是等边是就输出等边然后return结果等边三角形那一组数据永远只输出一行判错。正确的写法是把判断拆成互不干扰的三段第一段用if-else if输出角类型保证三选一第二段独立if输出等腰第三段独立if输出等边。三段之间不要互相干扰不要提前return。这个结构我称为分块判断、顺序输出比把所有条件揉进一个大if-else链条里要清晰得多。另外提一个有意思的数学事实整数边长的等腰直角三角形是不存在的。设两条直角边都是a斜边就是a√2要让斜边也是整数a必须是非零有理数乘√2还能得到整数这不可能。所以在整数输入下Right triangle和Isosceles triangle同时出现这种情况根本不会发生。知道这一点不能帮你少写代码但能帮你在对拍时判断某组输出是否可疑——如果程序在整数输入下输出了直角加等腰多半是判断逻辑串了。3. 从思路到代码完整实现与逐行解读3.1 C 版本整数运算一次排序走天下先给完整实现再逐段说为什么这么写#include bits/stdc.h using namespace std; int main() { long long a[3]; cin a[0] a[1] a[2]; sort(a, a 3); long long x a[0], y a[1], z a[2]; // 排序后只需比较最小两边之和与最大边 if (x y z) { cout Not triangle\n; return 0; } long long left x * x y * y; long long right z * z; if (left right) cout Right triangle\n; else if (left right) cout Acute triangle\n; else cout Obtuse triangle\n; if (x y || y z) cout Isosceles triangle\n; if (x y y z) cout Equilateral triangle\n; return 0; }几个设计决策值得说一下。用cin连续读三个数而不是按行读是因为cin会自动跳过空白字符输入是空格分隔还是换行分隔都无所谓这在评测环境里能省掉一类格式问题。用数组接输入而不是三个独立变量是为了能直接sortsort(a, a3)对三个元素排序的开销可以忽略但省去了手写交换逻辑。Not triangle分支里的return 0是有意为之。一旦确认构不成三角形后面所有平方比较都没有意义直接终止杜绝程序继续跑下去输出脏数据的可能。我见过有人这里用if-else包住后面全部逻辑嵌套三层之后自己都看不清括号配对用提前返回是最省心的写法。角类型判断用if-else if-else三选一等腰和等边用两个独立if这个结构对应题目多条件叠加的要求。注意等边判断没有写成else if因为等边一定等腰写成else if会让等边三角形永远等不到第二行输出。3.2 Python 版本短是短读入要小心Python 写起来更短但读入环节有个坑必须先处理import sys data sys.stdin.read().split() x, y, z sorted(map(int, data[:3])) if x y z: print(Not triangle) sys.exit(0) left, right x * x y * y, z * z if left right: print(Right triangle) elif left right: print(Acute triangle) else: print(Obtuse triangle) if x y or y z: print(Isosceles triangle) if x y z: print(Equilateral triangle)为什么不用input().split()因为那个写法默认三个数在同一行。虽然这道题的题面通常就是一行三个数但评测环境里换行读入的情况并不罕见用sys.stdin.read().split()一次性把所有空白分隔的内容读进来无论空格、换行、制表符都不会出错。这是我自己写题时的默认模板多打几个字符换一份安心。sorted()返回的是新列表直接解包成三个变量比用索引取干净。x y z是 Python 特有的链式比较等价于x y and y z读起来更像数学写法。sys.exit(0)和 C 的return 0作用一致确认不是三角形之后立即结束别让程序继续往下跑。有人会问Python 的大整数没有溢出问题为什么还要提long long因为在 C 那边有而且这道题的边长上限一万只是个具体数字换一道边长上限 10⁹ 的题平方后就是 10¹⁸ 量级必须上 64 位。养成用大一点的类型的习惯比每次都要停下来算一遍边界要高效得多。3.3 测试用例设计我习惯跑的这八组写完代码不许直接交先自己跑一遍。我固定用下面这组数据过一遍覆盖直角、锐角、钝角、等腰、等边、退化、乱序七种情况输入排序后判据过程期望输出3 4 53 4 57591625Right triangle3 3 33 3 363189Acute triangle/Isosceles triangle/Equilateral triangle2 3 42 3 454491316Obtuse triangle5 5 75 5 71075049Acute triangle/Isosceles triangle10 10 1910 10 192019200361Obtuse triangle/Isosceles triangle1 2 31 2 333Not triangle1 1 21 1 222Not triangle5 4 33 4 5同第一组Right triangle第三组2 3 4和第四组5 5 7是我特意选的临界钝角/临界锐角样本。2 3 4的平方和比斜边平方小 3是非常典型的钝角5 5 7的平方和只比斜边平方大 1稍微算错一点就会误判成直角拿来检验整数比较是否写对特别有效。第五组10 10 19用来打脸等腰一定是锐角的错觉。等腰三角形的顶角完全可以超过 90 度输出Obtuse加Isosceles两行才正确。第六、七组是退化三角形专门验证那个有没有写成。第八组是乱序输入验证排序有没有真的生效——如果没排序还硬套公式这组必然出错。4. 踩坑实录新手在这道题上翻车的六个位置4.1 高频错误对照表先看别人怎么错带人做题这些年这道题上的错误高度集中就那么几类我整理成表写完代码逐行对照一遍比漫无目的地读代码有效错误现象根本原因正确做法3 1 2判成能构成三角形没排序就只写abc先排序再比较xy与z1 2 3判成能构成三角形三角形不等式写成而不是判掉用xyz判定不能构成等边三角形少输出等腰那一行判断用了else if嵌套等腰、等边用两个独立if直角三角形判成锐角平方比较方向写反记牢和大为锐和小为钝某组数据随机答案错误用了sqrt或double比较一律整数平方比较编译没过或运行崩溃数组越界或未初始化用vector或固定长度数组并全部赋值表格里最后一条看着不起眼但确实发生过有人为了少写变量把三条边塞进长度 2 的数组里编译能过运行时读到垃圾值输出什么全看运气。这类问题在本地小数据上可能碰巧正确一到评测机就原形毕露。还有一个隐蔽的坑是判断完角类型后忘了继续判断等腰。因为角类型那三个分支里有的写了return有的没写逻辑不统一导致某些输入走到一半就退出了。我的建议是除了Not triangle这一处提前返回其余位置一律不用return让程序自然执行到最后一行输出结构一目了然。4.2 浮点数的诱惑sqrt和acos的灾难很多人学完余弦定理第一反应是算出角再判断C acos((x*xy*y-z*z)/(2.0*x*y))然后看C和π/2谁大。思路完全正确但工程上是自找麻烦。第一浮点数的相等比较本身就不可靠。理论上直角对应分子恰好为 0但x*xy*y-z*z是整数运算结果是精确的 0一转成double再参与除法、再进acos中间任何一步都可能引入误差得到1.5707963267948966或者1.5707963267948968你再用C M_PI/2去比大概率不成立。工程上正确的做法是设一个容差eps1e-9判断fabs(C - M_PI/2) eps但这又多引入一个容差取多大的调参问题边长极小时误差会被放大容差太小判不出直角太大又把接近直角的锐角误判成直角。第二整数平方比较是零成本的精确方案。x*xy*y和z*z都是整数相等就是相等不等就是不等不存在模糊地带。既然题目给的是整数边长判据又能完全用整数表达为什么要绕到实数域再绕回来第三浮点运算慢且不可移植。虽然这道题的数据规模小到无所谓性能但养成能用整数就不用浮点的习惯在计算几何题里能救命。我自己的原则是只要比较的是平方和的大小关系永远不开放根号只有在需要输出具体长度值的时候才在最后一步开方。4.3 几条只有踩过才记得住的心得第一条心得先写不能构成的分支再写其余。原因是这个分支是唯一的否定分支逻辑最简单先把它落地能立刻锁定一半的边界问题。而且一旦确认不能构成后面的所有判断在逻辑上都不该执行用提前返回把这个约束强制住比事后检查要可靠。第二条心得把排序后变量含义变了这件事写在注释里。我自己的模板会在排序那行后面加一句// 此后 xyz看着多余但隔一周回来看代码时这一行能省掉三分钟的重新推理。团队协作时更有用别人接手你的代码第一眼就知道变量约定。第三条心得输出字符串统一用复制粘贴不要手敲。我建议把五个答案先单独写在一个地方检查拼写无误后面所有输出语句从这里复制。手敲Isosceles出错概率极高而这类错误调试成本又不低属于典型的预防成本远低于修复成本。第四条心得本地测试一定要测乱序输入。这道题的正确解法对顺序不敏感错误解法对顺序极度敏感所以乱序数据是区分对错最快的探针。我通常准备至少两组乱序一组是普通乱序一组是最大边在第一位的极端乱序后者专门打击默认最后一个是最大边的错觉。5. 把这道题做厚三个值得动手的改造方向5.1 改造一从三条边长换到三个坐标点题目给的是边长但实际场景里更常见的是给三个点的坐标让你判断它们构成什么三角形。这时候第一步不是算边长而是先判断三点是否共线——共线就是退化直接输出不能构成三角形。判断共线用向量叉积设三点A(x1,y1)、B(x2,y2)、C(x3,y3)向量AB (x2-x1, y2-y1)AC (x3-x1, y3-y1)叉积cross AB.x * AC.y - AB.y * AC.x。cross 0表示三点共线此时三角形退化成一条线段。这个判据同样是纯整数运算不需要开方也不会引入精度问题。确认不共线之后把三条边长算出来AB² (x2-x1)² (y2-y1)²其余同理。此时拿到的是边长的平方正好——因为后面判断直角、锐角、钝角本来就是比较平方和根本不用开方。排序三个平方值用同样的判据走一遍就完成了分类。这个改造把坐标几何和三角形分类两个知识点串在一起是很典型的组合练法。这里有个细节要注意坐标版的边长平方可能很大坐标范围给到 10⁴ 的话单条边的平方就是2×10⁸三条边平方相加是4×10⁸还在 32 位范围内但如果坐标范围到 10⁹平方就是 10¹⁸ 量级必须用 64 位整数。这就是前面说的用大类型省心的实际价值。5.2 改造二多组数据直到输入结束原始题目只处理一组数据改造一下变成多组考验的是循环结构和状态重置。C 的写法是把主逻辑包在while (cin a[0] a[1] a[2])里cin在读到文件末尾时返回假循环自然结束Python 则用for line in sys.stdin逐行处理或者一次读完再按三个一组切片。多组数据最容易出的问题是状态没重置。如果上一组的某个中间变量影响了下一组错误会以某些组对某些组错的形式出现极难定位。我的做法是把每组数据的处理逻辑封装成一个函数所有中间变量都在函数内部定义天然隔离不共享任何状态。这个习惯在后续做更复杂的模拟题时价值更大。还有一个配套问题是输出格式。多组数据如果题目要求每组之间空一行就要严格控制如果要求连续输出就不能多打空行。这类格式要求在评测里同样是硬性判错不要觉得多一个空行无所谓。5.3 改造三把整数换成实数边长前面反复强调用整数平方比较是因为整数输入下这套判据无懈可击。但如果边长是实数呢比如输入是3.0 4.0 5.0或者更刁钻的1 1 1.41421356整数方案就用不了了。实数版本的思路是把所有判据改成带容差的比较。三角形不等式改成x y z eps其中eps取一个相对于数据规模合适的值比如1e-9平方比较改成fabs(left - right) eps判直角left right eps判锐角left right - eps判钝角。容差的选取原则是大于浮点误差的累积量小于题目关心的最小差异。边长误差如果是1e-6量级平方后误差会放大到1e-5量级eps就得取1e-5以上。这个改造的意义在于让你真正理解为什么整数输入下不需要容差——不是因为浮点不好而是因为这道题的判据恰好可以用整数精确表达。判据一旦涉及开方或者三角函数容差就是绕不过去的工程现实。想清楚这一点以后再遇到类似题目先问自己一句能不能全程用整数答案能就坚决用整数不能就老老实实设计容差。我平时做完一道入门题习惯花十分钟想想如果条件变一下会怎样这三个改造方向就是这么来的。它们不会让这道题的分数变高但会让这道题的价值从一道入门题变成一个可以反复复用的分类模板——三角形不等式、排序化简、平方判角度、多条件叠加输出这四个模块在后面很多计算几何题目里都会以各种形式重新出现。真正记住它们的方式不是背代码而是像这样把边界一个个推开看模板在哪一步失效、失效之后怎么补。补过一次下次再遇到就不用重新想了。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。