题解:洛谷 P1163 银行贷款
【题目来源】
【题目描述】
当一个人从银行贷款后,在一段时间内他(她)将不得不每月偿还固定的分期付款。这个问题要求计算出贷款者向银行支付的利率。假设利率按月累计。
【输入】
三个用空格隔开的正整数。
第一个整数表示贷款的原值 \(w_0\),第二个整数表示每月支付的分期付款金额 \(w\),第三个整数表示分期付款还清贷款所需的总月数 \(m\)。
【输出】
一个实数,表示该贷款的月利率(用百分数表示),四舍五入精确到 \(0.1\%\)。
【输入样例】
1000 100 12
【输出样例】
2.9
【核心思想】
-
问题分析:已知贷款总额 \(w_0\)、每月等额还款 \(w\) 和还款总月数 \(m\),求月利率 \(x\)(用百分数表示,精确到 \(0.1\%\))。每月还款后剩余欠款遵循“先计息后还款”的规则,即 \(余额 = 余额 \times (1 + x) - w\)。这是一个数值求解问题,函数 \(f(x)\) 表示在月利率 \(x\) 下还完 \(m\) 个月后的剩余欠款。当 \(f(x) = 0\) 时,\(x\) 即为所求利率。由于 \(f(x)\) 随 \(x\) 单调递增,可以用浮点二分在可行域内逼近零点。
-
算法选择:
- 浮点二分(实数二分):月利率 \(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\%\))即可保证四舍五入正确。
-
关键步骤:
- 读入:读取 \(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),将月利率转换为百分数并保留一位小数。
-
时间/空间复杂度:
- 时间复杂度:\(O(m \times \log(\text{范围}/\text{精度}))\),二分约 \(50\) 次迭代,\(m\) 通常较小,完全可行。
- 空间复杂度:\(O(1)\)。
-
浮点二分 + 金融模型的核心思想:
- 单调性利用:月利率越高,每月产生的利息越多,导致剩余欠款越大。函数 \(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
浙公网安备 33010602011771号