P1024 [NOIP 2001 提高组] 一元三次方程求解

记录115

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const double eps=1e-4;//浮点数会有一定的误差,epsilon浮点数比较时允许的误差范围(精度容差) 
double a,b,c,d;
double f(double x){
	return a*x*x*x+b*x*x+c*x+d;
} 
int main(){
	cin>>a>>b>>c>>d;
	for(int i=-100;i<=100;i++){//fabs()函数,浮点数的绝对值 
		double L=i,R=i+1,mid;//数<1e-4 时候,当它就近似是0了 
		if(fabs(f(L))<eps) cout<<fixed<<setprecision(2)<<L<<" ";//左端点是根输出 
		else if(fabs(f(R))<eps) continue;//右端点是根跳过,因为在下一轮循环,它是左端点(去重) 
		else if(f(L)*f(R)<0){//如果两点之间存在根 
			while(R-L>eps){//两个界限没有重合 
				mid=(L+R)/2;//二分答案来找 
				if(f(mid)*f(R)>0) R=mid;//向右边靠拢找 
				else L=mid;//向坐标左边靠拢 
			}
			cout<<fixed<<setprecision(2)<<L<<" ";//输出 
		}
	}
	return 0;//结束程序 
}

前言

我是一名专注信奥赛(CSP-J/S、NOIP)的教练。

  • 如果你觉得这篇题解对你有帮助,欢迎点击关注我的CSDN账号,我会持续更新高质量算法解析。
  • 我深知算法思维的构建远比单纯通过题目更重要,本系列题解不局限于AC代码的堆砌,而是致力于拆解题目背后的逻辑链条与核心知识点
  • 备赛路上若遇瓶颈,欢迎随时评论或私信,我将甄选典型疑难问题,通过视频讲解或撰写专项文章的形式,为你提供深度答疑。

题目传送门https://www.luogu.com.cn/problem/P1024


突破口

有形如:ax3+bx2+cx+d=0 这样的一个一元三次方程。给出该方程中各项的系数(a,b,c,d 均为实数),并约定该方程存在三个不同实根(根的范围在 −100 至 100 之间),且根与根之差的绝对值 ≥1。要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后 2 位。

提示:记方程 f(x)=0,若存在 2 个数 x1​ 和 x2​,且 x1​<x2​,f(x1​)×f(x2​)<0,则在 (x1​,x2​) 之间一定有一个根。


🔍 一、题目核心要求

🎯 问题本质

给定一个一元三次方程:

f(x)=ax3+bx2+cx+d=0f(x)=ax3+bx2+cx+d=0

已知:

  • 存在三个不同的实根
  • 所有根 ∈ [−100, 100]
  • 任意两根之差的绝对值 ≥ 1(关键!)

要求:

  • 从小到大输出三个实根
  • 保留两位小数

✅ 这是一个数值求根问题 ,不能用公式法(卡丹公式复杂且易出错),需用数值逼近方法

🧠 二、核心思路:利用"根隔离" + "二分法"

关键提示解析

若 x1<x2x1​<x2​ 且 f(x1)⋅f(x2)<0f(x1​)⋅f(x2​)<0 ,则 (x1,x2)(x1​,x2​) 内必有一实根。

这是介值定理的直接应用(函数连续 ⇒ 符号变化 ⇒ 必有零点)。

利用"根间距 ≥1"的性质

  • 因为任意两根距离 ≥1,所以在区间长度为 1 的小区间 [i, i+1] 中:
    • 最多包含一个根
    • 不会出现多个根挤在一个小区间导致漏检

💡 这是本题能用"步长为1扫描"的理论基础

算法策略

  1. 遍历所有整数区间i 从 −100 到 99,考察区间 [i, i+1]
  2. 对每个区间:
    • f(i) ≈ 0i 是根,直接输出
    • f(i+1) ≈ 0 → 跳过(下一轮 i+1 会作为左端点处理,避免重复)
    • f(i) * f(i+1) < 0 → 区间内有唯一根,用二分法逼近
  3. 由于总共只有 3 个根,循环中恰好会找到 3 次,按 i 递增顺序输出即为从小到大

代码分析

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const double eps = 1e-4; // 浮点误差容忍度
  • eps = 1e-4:用于判断"是否接近0"
  • 为什么不是 1e-7?因为最终只需 2 位小数精度1e-4 足够保证四舍五入正确
cpp 复制代码
double a, b, c, d;
double f(double x) {
    return a*x*x*x + b*x*x + c*x + d;
}
  • 定义多项式函数 f(x),避免重复写表达式
cpp 复制代码
int main() {
    cin >> a >> b >> c >> d;
  • 读入系数(均为实数)
cpp 复制代码
    for(int i = -100; i <= 100; i++) {
        double L = i, R = i + 1, mid;
  • 遍历每个单位区间 [i, i+1]
  • 注意:i 从 −100 到 100 (含),但 R = i+1 最大为 101
  • 实际有效区间是 [−100, 100],而根在 [−100,100] 内,所以覆盖完整

情况 1:左端点是根

cpp 复制代码
        if(fabs(f(L)) < eps) 
            cout << fixed << setprecision(2) << L << " ";
  • fabs(f(L)) < eps:判断 f(L) ≈ 0
  • 直接输出 L(即 i),保留两位小数
  • 使用 fixedsetprecision(2) 确保格式

情况 2:右端点是根(跳过,防重复)

cpp 复制代码
        else if(fabs(f(R)) < eps) 
            continue; // 下一轮 i=i+1 时,R 成为新的 L,会被输出
  • 避免同一个根被输出两次
  • 例如根为 2.0,则在 i=1R=2 是根,跳过;i=2L=2 是根,输出

✅ 巧妙去重!

情况 3:区间内有根(符号变化)

cpp 复制代码
        else if(f(L) * f(R) < 0) {
  • f(L)f(R) 异号 → 中间有根
cpp 复制代码
            while(R - L > eps) {
                mid = (L + R) / 2;
                if(f(mid) * f(R) > 0) 
                    R = mid;   // f(mid) 与 f(R) 同号 → 根在左半
                else 
                    L = mid;   // f(mid) 与 f(R) 异号 → 根在右半
            }

🔍 二分法细节:

  • 终止条件R - L ≤ eps(区间足够小)
  • 更新策略
    • f(mid)f(R) 同号 → 说明 midR 在根的同一侧 → 根在 [L, mid]R = mid
    • 否则 → 根在 [mid, R]L = mid

💡 也可以用 f(L)*f(mid) < 0 判断左半,等价。

cpp 复制代码
            cout << fixed << setprecision(2) << L << " ";
        }
    }
    return 0;
}
  • 输出近似根 L(此时 L ≈ R ≈ 真实根
  • 由于 i 从小到大遍历,输出自然有序

⚠️ 关键设计与易错点

问题 说明
eps 选择 1e-4 足够保证两位小数正确(误差 < 0.005)
去重机制 右端点根跳过,左端点输出,避免重复
根的顺序 i 递增扫描,找到的根自然从小到大
浮点比较 永远不要用 == 比较浮点数,必须用 fabs(x) < eps
区间覆盖 i 到 100,确保 [99,100] 被检查,根在 [−100,100] 内全覆盖

为何不用牛顿迭代或其他方法?

  • 牛顿法:需要导数,且可能不收敛(对初值敏感)
  • 公式法:三次方程有解析解,但涉及复数运算和三角函数,NOIP 环境下易出错
  • 本题特性 (根隔离 + 间距 ≥1)使得简单二分扫描 成为最优解:
    • 稳定、可靠、易实现
    • 时间复杂度:200 个区间 × log₂((1)/1e-4) ≈ 200 × 14 = 2800 次函数求值,极快
相关推荐
田梓燊8 小时前
力扣:23.合并 K 个升序链表
算法·leetcode·链表
re林檎8 小时前
算法札记——4.27
算法
AI人工智能+电脑小能手9 小时前
【大白话说Java面试题】【Java基础篇】第15题:JDK1.7中HashMap扩容为什么会发生死循环?如何解决
java·开发语言·数据结构·后端·面试·哈希算法
数据牧羊人的成长笔记9 小时前
逻辑回归与Softmax回归
算法·回归·逻辑回归
郑州光合科技余经理9 小时前
同城O2O海外版二次开发实战:从支付网关到配送算法
开发语言·前端·后端·算法·架构·uni-app·php
张健115640964811 小时前
使用信号量限制并发数量
开发语言·c++
jc062011 小时前
6.1云原生之Docker
c++·docker·云原生
Mrlxl.cn12 小时前
计算机网络——网络层
c语言·数据结构·计算机网络·考研
d111111111d12 小时前
STM32-UART封装问题解析
笔记·stm32·单片机·嵌入式硬件·学习·算法