仅根据BMI反推身高和体重?还真能做到!只需要简单的数论知识~

引言

本文视频版传送门

视频封面:

今天在castelu大佬评论区看到这样一条评论:

我们不禁要问:能否仅根据BMI反推身高和体重?答案是肯定的,但有没有更优雅的做法?

本文 52pojie: www.52pojie.cn/thread-2132...

本文 博客园: www.cnblogs.com/hans77/arti...

本文 juejin: juejin.cn/post/769633...

作者:hans774882968以及hans774882968以及hans774882968以及hans77

建模前的约定

我们知道BMI的计算公式:
B=wh2 B=\frac{w}{h^2} B=h2w

为了方便数学建模,不妨先结合现实生活进行一些约定:

  1. 体重的单位是kg,所以精确到1位小数,比如114.5kg
  2. BMI使用的身高的单位是米,所以身高精确到3位小数,比如1.919米
  3. 有了前两条约定,B显然是有理数,不妨设为 PQ \frac{P}{Q} QP

初步思路

进行上述约定后,凭Hans多年的算法竞赛经验(金(dǎ)牌(tiě)水平 ),一瞬间就想到,约定一个身高上限(因为很难见到3米以上的人类),便能直接枚举范围内所有可能的身高,用公式 w=Bh2w=Bh^2 w=Bh2 看何时能得到一位小数的体重即可。

虽然能解决问题,但局限性有点强啊......有没有更好的方法呢?有的。

Hans的想法:质因数分解

回顾BMI的计算公式,令 W=10w, H=1000hW=10w,\ H=1000h W=10w, H=1000h ,则W和H都为正整数,代入BMI公式:
PQ=wh2= W10 (H1000) 2 =105WH2  ⟹  W= PH2105Q (1) \frac{P}{Q}=\frac{w}{h^2}=\frac{\frac{W}{10}}{\left( \frac{H}{1000} \right)^2}=10^5 \frac{W}{H^2} \implies W=\frac{PH^2}{10^5Q} \tag{1} QP=h2w=(1000H)210W=105H2W⟹W=105QPH2(1)

这里同学们不妨思考一下:为什么不变形为 H2H^2 H2 等于xxx,而是这样变形呢?

也就是说,我们希望找到最小的正整数H,使得 PH2PH^2 PH2 是 105Q10^5Q 105Q 的倍数,这就是道数论题了。为了方便,不妨设BMI已经是最简分数,即P和Q互素,于是P对倍数关系没有影响,问题转化为:

求最小的正整数H,使得 H2H^2 H2 是 105Q10^5Q 105Q 的倍数

这时,等式的分子只剩H,足够纯净,我们便不难想到:考虑 D=105QD=10^5Q D=105Q 的质因数分解
D= p1α1 p2α2 ⋯ pkαk D = p_1^{\alpha_1} p_2^{\alpha_2} \cdots p_k^{\alpha_k} D=p1α1p2α2⋯pkαk

为了让 H2H^2 H2 包含 DD D 的所有质因数, HH H 至少要包含 ⌈αi/2⌉ \lceil \alpha_i / 2 \rceil ⌈αi/2⌉ 个 pi p_i pi ,因此,最小的
H= ∏i=1k pi ⌈αi/2⌉ H = \prod_{i=1}^{k} p_i^{\lceil \alpha_i / 2 \rceil} H=i=1∏kpi⌈αi/2⌉

对D质因数分解后立刻就能求出H,进而求出W。数据范围不太大的情况下,直接用试除法对D质因数分解即可,时间复杂度 O(D) O(\sqrt{ D }) O(D ) 。最后,我们把P和Q互素的假设去掉,1式只需稍作修改:
W= PgH2 105Qg , g=gcd(P,Q) W=\frac{\frac{P}{g}H^2}{10^5 \frac{Q}{g}},\ g=gcd(P,Q) W=105gQgPH2, g=gcd(P,Q)

因为 Pg \frac{P}{g} gP 和 Qg \frac{Q}{g} gQ 互素,所以只需要把上面假设的D改成 D=105Qg D=10^5 \frac{Q}{g} D=105gQ

完整代码:

python 复制代码
import math
from fractions import Fraction


def prime_factors(n):
    """对整数 n 进行质因数分解,返回字典 {素数: 幂次}"""
    factors = {}
    while n % 2 == 0:
        factors[2] = factors.get(2, 0) + 1
        n //= 2
    d = 3
    while d * d <= n:
        while (n % d) == 0:
            factors[d] = factors.get(d, 0) + 1
            n //= d
        d += 2
    if n > 1:
        factors[n] = factors.get(n, 0) + 1
    return factors


def find_exact_hw(bmi_value):
    """
    根据精确的有理数 BMI,构造出最小的一组
    身高精确到3位小数、体重精确到1位小数的 (h, w)
    """
    if isinstance(bmi_value, float):
        bmi_frac = Fraction(bmi_value).limit_denominator(10**9)
    else:
        bmi_frac = Fraction(bmi_value)

    P = bmi_frac.numerator
    Q = bmi_frac.denominator

    if P <= 0 or Q <= 0:
        raise ValueError("BMI 必须是正有理数")

    # 1. 计算有效分母 D = 100000 * Q / gcd(P, 100000 * Q)
    denom = 100000 * Q
    g = math.gcd(P, denom)
    D = denom // g

    # 2. 对 D 进行质因数分解
    factors = prime_factors(D)

    # 3. 构造最小的 H,使得 H^2 是 D 的倍数
    H = 1
    for p, alpha in factors.items():
        # 幂次向上取整:ceil(alpha / 2)
        power = (alpha + 1) // 2
        H *= (p ** power)

    # 4. 计算对应的 W = P * H^2 / (100000 * Q)
    # 因为 H 的构造保证了 H^2 是 D 的倍数,这里必定能整除
    W = (P * H * H) // (100000 * Q)

    # 还原为3位小数的身高和1位小数的体重
    h = H / 1000.0
    w = W / 10.0

    return h, w, H, W


if __name__ == "__main__":
    test_cases = [
        25,             # 整数
        18.5,           # 常见 BMI
        "3.14159",      # 字符串输入,避免浮点误差
        Fraction(1, 3),  # 分数 1/3
        Fraction(22, 7),  # 分数 22/7
        Fraction(560000, 29241),  # 56kg 1.71m
        Fraction(565000, 29241),  # 56.5kg 1.71m
        Fraction(65000, 3249),  # 58.5kg 1.71m
        Fraction(30625, 1681),  # 49kg 1.64m
        Fraction(5952000, 104329),  # 595.2kg 3.23m
        Fraction(28625000, 1846881),  # 114.5kg 2.718m
        114.514,
        810.1919,
    ]

    for bmi in test_cases:
        h, w, H, W = find_exact_hw(bmi)
        print(f"目标 BMI: {bmi} | 最小解: h={h:<5}米, w={w:<5}千克 | 验证: {w} / {h}^2 = {w / (h**2):.5f}")

输出:

ini 复制代码
目标 BMI: 25 | 最小解: h=0.2  米, w=1.0  千克 | 验证: 1.0 / 0.2^2 = 25.00000
目标 BMI: 18.5 | 最小解: h=1.0  米, w=18.5 千克 | 验证: 18.5 / 1.0^2 = 18.50000
目标 BMI: 3.14159 | 最小解: h=100.0米, w=31415.9千克 | 验证: 31415.9 / 100.0^2 = 3.14159
目标 BMI: 1/3 | 最小解: h=3.0  米, w=3.0  千克 | 验证: 3.0 / 3.0^2 = 0.33333
目标 BMI: 22/7 | 最小解: h=3.5  米, w=38.5 千克 | 验证: 38.5 / 3.5^2 = 3.14286
目标 BMI: 560000/29241 | 最小解: h=0.855米, w=14.0 千克 | 验证: 14.0 / 0.855^2 = 19.15119
目标 BMI: 565000/29241 | 最小解: h=1.71 米, w=56.5 千克 | 验证: 56.5 / 1.71^2 = 19.32218
目标 BMI: 65000/3249 | 最小解: h=0.57 米, w=6.5  千克 | 验证: 6.5 / 0.57^2 = 20.00616
目标 BMI: 30625/1681 | 最小解: h=1.64 米, w=49.0 千克 | 验证: 49.0 / 1.64^2 = 18.21832
目标 BMI: 5952000/104329 | 最小解: h=1.615米, w=148.8千克 | 验证: 148.8 / 1.615^2 = 57.05029
目标 BMI: 28625000/1846881 | 最小解: h=2.718米, w=114.5千克 | 验证: 114.5 / 2.718^2 = 15.49910
目标 BMI: 114.514 | 最小解: h=10.0 米, w=11451.4千克 | 验证: 11451.4 / 10.0^2 = 114.51400
目标 BMI: 810.1919 | 最小解: h=100.0米, w=8101919.0千克 | 验证: 8101919.0 / 100.0^2 = 810.19190

最后公布前面思考题的答案:

  1. 为什么不变形为 H2H^2 H2 等于xxx,而是这样变形呢?因为验证一个分式是完全平方数 虽然可行(同学们不妨动手试试),但相比于验证一个分式是正整数,更为困难
相关推荐
追光者493 小时前
我把 55 篇自动化日志翻了一遍:最容易出事的是这 4 个地方
python·自动化运维
2601_962885723 小时前
如何用 Python计算 CR能量指标(中间意愿指标)?
开发语言·python
打工仔折腾 AI3 小时前
LangChain 工具调用实战:从 Tool 定义到 Agent 执行链路拆解
java·前端·人工智能·后端·python·langchain·ai agent 实战
hhb_6183 小时前
Flask+SQLite实现用户管理接口
python·sqlite·flask
全栈练习生3 小时前
RAG 准确率保障的体系化方案
python·ai
秋秋小事3 小时前
python 基础知识总结(一)
python
蜗牛互联网3 小时前
Python调用Gemini Structured Outputs实现工单路由门禁
java·开发语言·人工智能·python
TomEval3 小时前
【测AI】第12篇:NLP / CV / 语音 —— 三大 AI 方向速通
人工智能·python·自然语言处理·aigc·测试用例
SEO_juper3 小时前
给网页做可引用性评分:判断内容够不够让 AI 引用(GEO 工具,附代码)
开发语言·人工智能·爬虫·python·seo·独立站