关于二分答案输出误差问题的看法

  • 对于在整数域上的二分答案问题的输出一般情况下是不会出现问题的,因为不会涉及到精度和类型转换。
  • 但是对于在实数域上的二分时,可能出现一些奇奇怪怪的问题。
    例题:POJ2018
    1. 这道题是一个实数域上的二分,但是让输出的是整数。
    2. 首先,对于在实数域上的二分来说,答案是会有无法避免的误差的,无论多么精确。比如:真正的答案是1,但是当l=0.9,r=1的时候,无论二分多少次,都只会使得l接近1,但是永远也不会到达1。这就导致了二分出的答案本身就是有误差的。
    3. 如果我们输出格式如果是double类型的话,还是不会出现什么问题,因为本身输出时,采用的机制是四舍五入,会消除这一点小误差。
    4. 但是,如果输出的是整数类型的话,我们知道,在强制转换时,输出采用的机制是截断。因此,无论如何也不会四舍五入,导致最后输出和标准答案的值差1。
    5. 那么要如何才能解决这个问题呢?首先,一个最重要的问题是,标准答案究竟是采用什么形式进行输出的呢?这个可以从题目中得出,像这道题答案提示了是输出整数(integer),因此答案其实是输出了正确的那个平均值之后做了截断这个操作的。而我们现在的问题是本身平均值就是不对的,那么可以采用什么样的方式来弥补呢?
    6. 第一种方式:采用手动四舍五入。即在乘1000之后进行加0.5。不过这样还是会有几个点WA掉,因为会出现下面这种情况,即:我们二分出的答案是正确的,但是在输出时还是选择了四舍五入,导致了比标准程序截断输出的答案大了1。因此,这种方式不行。
    7. 在叙述第二种方式之前,我们到此可以得出,必须按照标准程序的答案求解过程来求解,才能得出正确的答案,因此,问题其实是出在了如何才能得出正确的二分答案呢?其实,我们可以知道,虽然说二分的答案会出现一些精度问题,但是对于答案的区间是不会改变的,比如:正确的答案是在[1,5]这个区间上得到的,那么二分中最后的答案也是在这个区间求得的。因此,我们可以记录下二分答案的区间,在计算平均值的时候,采用原来序列进行计算,这样就会得出正确的平均值了。

代码如下:

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1e5+10;
double a[maxn],sum[maxn];
int n,l,ans_l,ans_r,ans_ll;
bool right(double x){
	double res=-1e10,mini=1e10;
	for(int i=1;i<=n;i++)sum[i]=sum[i-1]+a[i]-x;
	for(int i=l;i<=n;i++){
		if(mini>sum[i-l])ans_l=i-l,mini=sum[i-l];
		if(res<sum[i]-mini)ans_r=i,ans_ll=ans_l,res=sum[i]-mini;
	}
	return res>=0;
}
int main(){
	scanf("%d%d",&n,&l);
	for(int i=1;i<=n;i++)scanf("%lf",&a[i]);
	double l=0,r=1e6;
	for(int i=1;i<=100;i++){
		double mid=(l+r)/2.0;
		if(right(mid))l=mid;
		else r=mid;
	}
	double ans=0;
	for(int i=ans_ll+1;i<=ans_r;i++)ans+=a[i];
	ans/=(ans_r-ans_ll);
	printf("%d\n",int(1000*ans));
	return 0;
}
posted @ 2018-09-16 16:34  shellpicker  阅读(332)  评论(0编辑  收藏  举报