<p>三角形数定义为 $ T_n = \frac{n(n+1)}{2} $($ n $ 为正整数),判断正整数 $ x $ 是否为其等价于验证方程 $ n^2 + n - 2x = 0 $ 是否有正整数解,即检查 $ \sqrt{1 + 8x} $ 是否为奇整数且其平方等于 $ 1 + 8x $。</p>

什么是三角形数的数学定义
三角形数是形如 $ T_n = \frac{n(n+1)}{2} $ 的正整数,其中 $ n $ 是正整数。比如 1、3、6、10、15 都是三角形数。
判断一个正整数 x 是否为三角形数,本质是解方程 $ n^2 + n - 2x = 0 $,看是否存在正整数解 n。求根公式给出:
n = (-1 + sqrt(1 + 8*x)) / 2
所以关键步骤是:算出 sqrt(1 + 8*x),检查它是否为整数,且该整数减 1 后能否被 2 整除。
用 sqrt 判断时为什么容易出错
C++ 的 std::sqrt 返回 double,浮点精度在大整数(比如 > 1e13)附近会丢失,导致取整错误。例如:
- 对
x = 9999999999999,1 + 8*x是精确整数,但sqrt可能返回略小于真实值的数(如8944271.999999999),floor或强制转long long就会错判。
常见错误写法:
立即学习“C++免费学习笔记(深入)”;
long long root = sqrt(1 + 8LL * x); // 危险!没四舍五入,也没校验
正确做法应包含:
- 用
llround或手动加 0.5 后截断,避免向下取整偏差 - 必须验证
root * root == 1 + 8*x,不能只信sqrt结果 -
root必须是奇数(因为root = 2n + 1),否则(root - 1) / 2不是整数
C++ 实现:安全、可读、支持 long long
以下函数适用于 unsigned long long 范围内(约到 1e18):
bool isTriangular(unsigned long long x) {
if (x == 0) return false;
unsigned long long t = 1 + 8ULL * x;
unsigned long long root = llround(sqrt((long double)t));
if (root * root != t) return false;
if ((root & 1) == 0) return false; // root 必须为奇数
return true;
}说明:
- 用
long double提升sqrt精度(比double更稳妥) -
llround(来自<cmath>)比static_cast<long long>(sqrt(...))更可靠 - 显式检查
root * root == t是必须步骤,绕过所有浮点不确定性 - 不依赖
root % 2 == 1,而用位运算(root & 1),更快也更清晰
有没有不用浮点的纯整数解法
有,用二分查找解 n(n+1)/2 == x,时间复杂度 O(log x),完全规避浮点误差:
bool isTriangularInt(unsigned long long x) {
if (x == 0) return false;
unsigned long long lo = 1, hi = static_cast<unsigned long long>(sqrt(2.0 * x)) + 2;
while (lo <= hi) {
unsigned long long mid = lo + (hi - lo) / 2;
unsigned long long val = mid * (mid + 1) / 2;
if (val == x) return true;
if (val < x) lo = mid + 1;
else hi = mid - 1;
}
return false;
}注意:
- 初始上界用
sqrt(2.0 * x) + 2是保守估计,确保覆盖真实n -
mid * (mid + 1) / 2在mid很大时可能溢出,若输入上限极高(> 1e18),需加溢出检测 - 对单次判断,浮点法更快;对大量判断或要求绝对确定性,整数二分更稳
实际项目里,只要输入在 1e15 以内,带校验的 sqrt 版本足够快又足够准;超过这个量级,优先考虑整数二分,或者先用 __builtin_clzll 粗筛范围再查。浮点校验那一步,少写就全白忙。


















