题解:洛谷 P1163 银行贷款

【题目来源】

洛谷:P1163 银行贷款 - 洛谷

【题目描述】

当一个人从银行贷款后,在一段时间内他(她)将不得不每月偿还固定的分期付款。这个问题要求计算出贷款者向银行支付的利率。假设利率按月累计。

【输入】

三个用空格隔开的正整数。

第一个整数表示贷款的原值 \(w_0\),第二个整数表示每月支付的分期付款金额 \(w\),第三个整数表示分期付款还清贷款所需的总月数 \(m\)

【输出】

一个实数,表示该贷款的月利率(用百分数表示),四舍五入精确到 \(0.1\%\)

【输入样例】

1000 100 12

【输出样例】

2.9

【核心思想】

  1. 问题分析:已知贷款总额 \(w_0\)、每月等额还款 \(w\) 和还款总月数 \(m\),求月利率 \(x\)(用百分数表示,精确到 \(0.1\%\))。每月还款后剩余欠款遵循“先计息后还款”的规则,即 \(余额 = 余额 \times (1 + x) - w\)。这是一个数值求解问题,函数 \(f(x)\) 表示在月利率 \(x\) 下还完 \(m\) 个月后的剩余欠款。当 \(f(x) = 0\) 时,\(x\) 即为所求利率。由于 \(f(x)\)\(x\) 单调递增,可以用浮点二分在可行域内逼近零点。

  2. 算法选择

    • 浮点二分(实数二分):月利率 \(x\) 的范围为 \([0, +\infty)\),但实际不可能过高,通常取 \([0, 10]\)(即 \(0\% \sim 1000\%\))作为搜索区间。由于 \(f(x)\) 严格单调递增(\(x\) 越大,利息越多,剩余欠款越多),可以通过二分找到使 \(f(x) \ge 0\) 的最小 \(x\),即第一个还款不足(或恰好还清)的利率。
    • 检查函数 check(x):模拟 \(m\) 个月的还款过程,计算剩余欠款 \(s\)。若 \(s \ge 0\),说明利率 \(x\) 偏高或刚好(还款不足或恰好还清),则减小右边界;否则(\(s < 0\),提前还清),说明利率偏低,增大左边界。
    • 精度控制:输出要求保留 \(1\) 位小数百分比,即绝对误差小于 \(0.05\%\)。二分精度达到 \(10^{-5}\)(对应 \(0.001\%\))即可保证四舍五入正确。
  3. 关键步骤

    • 读入:读取 \(w_0\)(贷款原值)、\(w\)(每月还款)、\(m\)(还款月数)。
    • 定义检查函数bool check(double x)
      • 初始化 s = w0
      • 循环 \(i\) 从 1 到 \(m\)s = s * (1 + x) - w
      • s >= 0 返回 true,否则返回 false
    • 二分查找:左边界 l = 0,右边界 r = 10(确保包含真实利率)。
      • r - l > 1e-5 时:
        • mid = (l + r) / 2
        • check(mid) 为真,则 r = mid(尝试更小利率);
        • 否则 l = mid(尝试更大利率)。
    • 输出printf("%.1f", r * 100),将月利率转换为百分数并保留一位小数。
  4. 时间/空间复杂度

    • 时间复杂度:\(O(m \times \log(\text{范围}/\text{精度}))\),二分约 \(50\) 次迭代,\(m\) 通常较小,完全可行。
    • 空间复杂度:\(O(1)\)
  5. 浮点二分 + 金融模型的核心思想

    • 单调性利用:月利率越高,每月产生的利息越多,导致剩余欠款越大。函数 \(f(x)\) 单调递增,因此可以二分。
    • 模拟还款过程:通过循环精确模拟每月“计息-还款”的现金流,得到剩余欠款,避免了公式推导的复杂性。
    • 精度控制:浮点二分通过不断缩小区间达到所需精度,简单可靠。
    • 适用场景:适用于等额本息还款、投资回报率计算等金融领域数值求解问题,是浮点二分的经典应用。

【解题思路】

【算法标签】

普及 #浮点二分

【代码详解】

#include <bits/stdc++.h>
using namespace std;

// 全局变量:
// loan: 贷款金额
// pay: 每月还款金额
// month: 还款月数
// rate: 月利率(未使用)
// mid: 当前尝试的月利率
// l, r: 二分查找的左右边界
// t: 临时变量,用于计算剩余贷款
int loan, pay, month;
double rate, mid, l = 0, r = 10, t;

int main()
{
    // 输入贷款金额、每月还款金额和还款月数
    cin >> loan >> pay >> month;

    // 二分查找计算实际月利率
    while (l <= r)
    {
        mid = l + (r - l) / 2;  // 计算中间值,防止溢出

        // 当左右边界足够接近时终止循环
        if (r - l < 0.0001)
        {
            break;
        }

        t = loan;  // 初始化剩余贷款

        // 模拟按月还款过程
        for (int i = 1; i <= month; i++)
        {
            t = t * (1 + mid) - pay;  // 计算每月还款后的剩余贷款
        }

        // 根据计算结果调整搜索范围
        if (t > 0)  // 还款不足,利率过高
        {
            r = mid;
        }
        else if (t < 0)  // 还款过多,利率过低
        {
            l = mid;
        }
        else  // 刚好还清,直接输出
        {
            printf("%.1f", mid * 100);
            return 0;
        }
    }

    // 输出最终确定的月利率(转换为百分数)
    printf("%.1f", mid * 100);

    return 0;
}
// 使用老师介绍的板子再写一遍
#include <bits/stdc++.h>
using namespace std;

int a, b, c;  // a: 贷款总额, b: 每月还款额, c: 还款月数

// 检查函数:判断给定的月利率x是否可行
// 即按照利率x还款c个月后,剩余欠款是否>=0
bool check(double x)
{
    double s = a;  // s为未还的钱数(剩余欠款)

    for (int i = 1; i <= c; i++)
    {
        s = s * (1 + x) - b;  // 每月:剩余欠款先加上利息,再减去还款
    }

    return s >= 0;  // 如果剩余欠款>=0,说明利率过高或刚好
}

// 二分查找:寻找月利率x
double find()
{
    double l = 0, r = 10;  // 搜索月利率范围[0%, 1000%]

    while (r - l > 1e-5)  // 精度控制
    {
        double mid = (l + r) / 2;

        if (check(mid))  // 如果mid利率可行(过高)
        {
            r = mid;  // 尝试更小的利率
        }
        else
        {
            l = mid;  // 尝试更大的利率
        }
    }

    return r;  // 返回满足条件的最小月利率
}

int main()
{
    cin >> a >> b >> c;  // 输入贷款总额、每月还款额、还款月数
    double ans = find();  // 计算月利率
    printf("%.1f", ans * 100);  // 转换为百分比,保留一位小数
    return 0;
}
// 使用acwing的模板二刷
#include <bits/stdc++.h>
using namespace std;

int a, b, c;  // a:贷款总额, b:每月还款额, c:还款月数

// 检查函数:计算给定月利率x下,还款c个月后的剩余欠款
bool check(double x)
{
    double s = a;  // 初始欠款

    for (int i = 1; i <= c; i++)
    {
        s = s * (1 + x) - b;  // 每月:剩余欠款×(1+利率)- 还款额
    }

    return s >= 0;  // 如果剩余欠款>=0,说明利率过高或刚好
}

// 二分查找函数:寻找符合条件的月利率
double find()
{
    double l = 0, r = 10;  // 搜索范围:月利率0%~1000%
    double eps = 1e-5;     // 精度控制:0.001%

    while (r - l > eps)  // 当区间足够小时停止
    {
        double mid = (l + r) / 2;

        if (check(mid))  // 如果mid利率可行(剩余欠款>=0)
        {
            r = mid;  // 利率可能过高,尝试更小的利率
        }
        else
        {
            l = mid;  // 利率过低,尝试更大的利率
        }
    }

    return l;  // 返回左边界(最后一个不可行的利率)
}

int main()
{
    cin >> a >> b >> c;  // 输入数据

    double ans = find();  // 计算月利率
    printf("%.1f", ans * 100);  // 转换为百分比,保留1位小数

    return 0;
}

【运行结果】

1000 100 12
2.9
posted @ 2026-09-02 21:41  团爸讲算法  阅读(16)  评论(0)    收藏  举报