解一元三次方程方程问题细节探讨

解一元三次方程

虽然我是一个新手,按”规矩“应该要经常在博客里面把自己的错误都写出来做个总结,但是我比较保守,总是觉得博客是一个社交平台,一些相对不太成熟的东西还是写个word文档自己存起来好,在博客里面发出来的都是一些比较有感悟、用心搞过的东西(虽然可能还是不成熟的)。总而言之,用心创作,如有错漏,请多见谅、多指正。

前排放一下试错的过程:(其实我还拿洛谷试验了几次)openjudge上的提交记录
这道题之所以试验了这么多次(都是掩饰,倒不如说是WA了这么多次),就是因为反复试了很多细节。所以说,dalao们觉得平平无奇、不言自明的东西,到了新手那里都成了细节和新鲜事物(捂脸)。
那么这个题我到底品出来多少细节呢?直接上一段最终的代码:

#include<cstdio> 
using namespace std;
double a,b,c,d;
int count;
double ans(double x){
	return x * x * x * a + x * x * b + x * c + d;
}
void divide(double x1,double x2){
	if(x2 - x1 < 0.001){ //细节1 
		if(count > 0) printf(" ");
		printf("%.2lf",x1),count++;
		return;
	}
	double mid = (x1 + x2) / 2;
	if(ans(x1) * ans(mid) <= 0) divide(x1,mid);
	else if(ans(x2) * ans(mid) <= 0) divide(mid,x2);//细节2 
}
void solve(double x,double y){//细节3 
	double i;
	for(i = x;i <= y - 1 && count < 3;i++){
		if(ans(i) * ans(i + 1) <= 0) divide(i,i + 1);
		i += 0.5;//细节4 
	}
}
int main(){
	scanf("%lf %lf %lf %lf",&a,&b,&c,&d);//细节5 
	solve(-100,100);
	return 0;
}

(u1s1,我觉得五个细节试了十五六次也不算多)
接下来我们就分别讨论一下这五个细节。
首先这个题范围如此之大,假如要暴搜的话最坏的情况需要搜上20000次,肯定TLE无疑,因此显然是个分治 (更有说服力的解释是,这个题是分治里面的题)
有了这个思路,才好去看一些实际操作中的细节。
先从细节3、细节5开始,这俩其实异曲同工,说的都是变量类型的问题。细节3指的是,不要忘记函数的变量和放进去的变量要是同一个类型的;细节5指的是,如果想进行浮点数运算,就不要把浮点数跟整形混一起算(大多数需要浮点的题基本上输入也有浮点)。这些东西虽然是基础,但是对于一个新手还是经常错,而且这个还不太容易察觉(举个最浅显的例子,虽然我现在还是会scanf忘记打&,但是程序一输入就报错就会第一时间找自己是不是没有打&;不过如果是printf加了&,一般就很长时间才能发现,因为能让一段平常的代码输出一对很鬼畜的东西的不止这个),所以值得拿出来说一说。另外,一般把整型转成浮点最方便的方法就是*1.0,但是千万不要给写成 *0.1,以及像这种题通常都是要算一下百分比,还得记得 *100……
接下来的两个是细节1、细节4,这两个都与四舍五入有关系。
当年高中老师在信息课上讲Python的时候,曾经说过一个整数的四舍五入的很好的方法:把这个数据+0.5,然后取整。然而在细节1中我们直接取到了0.001而不是0.005(尽管这俩都能过),这是因为double取值的时候会自动的四舍五入而不是像整型的除法或者ceil那样简单粗暴的向下或上取整,因此如果出现正好卡在千分位是4或5的时候,取到0.005就会导致这个数从向下的那一边给拉到了向上的一边,而如果已经小于0.001了,那么这个数连千分位都可以说是确定的(或者说,其实更科学的取法应该是取到0.0005?),因此我们随便从x1、x2里面挑一个就行了。至于为什么不给x1加个数输出来替换掉这个膨胀的寻找范围,正如上所述,如果范围不够小,而加上的数又比较大(这里我认为超过0.001的都叫做大)很难保证这个区间内的随便哪一个值加上这个预设的数都不会移位;但是如果加上的数小,那么加与不加还有什么区别呢?结果还是不能保证这个区间内所有的数加上了都可以在正确的一侧,因为这个区间本身就太大了,要是把区间取得足够小,也就不必再费心考虑这个加一个数的问题了。
至于细节4,这个操作可能看起来比较迷惑,但是是有用处的。由于细节1,我们不得不把区间取得很小,加上这道题的限制是两个解的差至少是1,因此一个区间[i,i+1]搜过之后,如果这个解恰好就卡在i+1上,那么下次搜[i+1,i+2]的时候就会发生重复,所以如果确实找到一个解,就要额外增加一下i,人为防止出现取重的可能性。由于四舍五入原理加上原题的限制,我选择了+0.5,这是为了防止错过下一个区间的值而又可以防止取重复,如果+1的话就会导致跳过解的可能性。
最后一个就是细节2,也是整个题最玄学的一个点。我们一开始学二分的时候,老师都会特别提醒,说一定要在二分的时候写(l,mid)和(mid+1,r),千万不能把mid给丢了,否则会导致程序整个RE;然而这个题就是要写(l,mid)(mid,r),啥也不能加,0.01不行,0.001不行,连个基本已经没卵用的0.0001也不行,这是为什么?
要解答这个问题,我们需要知道基本的二分为什么一定要有这个+1(整型)。因为在一般的二分当中,结束条件的要求是极小,这个极小往往是l=r。如果不进行+1,就会导致l-r=1的时候总有mid和l的差值为1不变,结果这个二分就一直卡在这里,无法结束,直至RE。这种情况几乎会发生在每一个数据中,因此必须要+1。然而这题压根就不害怕自己mid取不出来,因为我们再怎么说,这个mid它是一个double,小数点后12位,万亿分位都能取,还怕这个才千分位的判断?而且我们并不需要取到l=r为止,只要取到一定范围之内就可以,同时mid的潜力完全可以满足这个范围的要求,因此不必要再加。既然可以不加,那么为什么加了就是不对呢?因为在加了之后,我们人为地改变了下一次的取值范围,这种改动一多,就会导致偏的越来越大,结果取不到需要的值,因为double毕竟是可以取12位,他允许我们忽略细节,自然就带来了巨大的误差空间,这个+0.001不要说是导致错过一些解,甚至都有可能导致一个区间直接被跳过去,尤其是如果某个解恰巧是个整数或者恰巧就等于r,这个加上的数就可能导致这个解被跳过去,不管到底加的是有多小(雄辩的事实是,尽管我加了个10^-12,也就是double的极限,还是能跳掉卡在r的整数解)。
以上就是这一道题产生的各种自认为所谓细节的讨论。总之其实多数还是一些基础性的问题,应该是平时要多注意的坑;其他的一些东西,就需要我们能严谨的思考,如果真出现了很玄学的东西,那我们也只能微笑着面对它,然后请它上博客“青史留名”了。
Thank you for reading!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值