先放一个样例的函数图像,这样直观…
这个是样例1:
最低点是0,这个很明显的,经过原点
这个是样例2:
交叉处的高度为0.5
虽然看过了样例,但貌似还是一脸懵逼…
- 给定n个二次函数f1(x),f2(x),…,fn(x)(均形如ax^2+bx+c),设F(x)=max{f1(x),f2(x),…,fn(x)},求F(x)在区间[0,1000]上的最小值
总结一下不就是最大值的最小值吗?好了,这题二分(裸裸的都是套路)
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cmath>
#include<string.h>
#include<algorithm>
using namespace std;
double read(void)//快读,不多说了
{
long long x=0,w=1,m=1,p=0;
char c=getchar();
for(;c<'0'||c>'9';c=1,c=getchar())
if(c=='-')w*=-1;
for(;c>='0'&&c<='9'||c=='.';c=1,c=getchar())
if(c>='0'&&c<='9')
{x=(x<<1)+(x<<3)+(c^48);m*=p?10:1;}
else p=1;
return (x*w*1.0-0.0+m)/(m*1.0+0.0)-1;
}
double Top(double a,double b,double c)//计算函数顶点的y坐标
{
//因为f(x)=ax^2+bx+c
//所以f'(x)=2*a*x+b
//因为顶点的切线的斜率为0
//所以2*a*x+b=0
//x=-b/2/a
//带入原式便得出一下的公式
double delta=b*b-4*a*c;
return -delta/4/a;
}
double f(double a,double b,double c,double x)//求出该函数在x时的值
{
return a*x*x+b*x+c;
}
double SPJ=0.00000001;//spj要设的小一点,不然会被卡精度
double A[10002],B[10002],C[10002],K[10002],BB[10002];//A,B,C分别记录二次函数的a,b,c;K,BB分别记录一次函数的k,b
double get_l(double a,double b,double c,double y)//给定一个二次函数(a!=0),与常数函数y上靠左的焦点
{
c=c-y;
double delta=b*b-4*a*c;
return (-b-sqrt(delta))/2/a;
}
double get_r(double a,double b,double c,double y)//靠右的焦点
{
c=c-y;
double delta=b*b-4*a*c;
return (-b+sqrt(delta))/2/a;
}
double get_one(double k,double b,double y)//求一个一次函数,与常数函数y上的焦点
{
return (y-b)/k;
}
bool check(double mid,int one,int two)//判断mid是否成立
{
int i;
double l=0,r=1000,L,R;
for(i=1;i<=two;i++)//先判断二次函数
{
L=get_l(A[i],B[i],C[i],mid);
R=get_r(A[i],B[i],C[i],mid);
if(R<l||L>r)return 0;//二次函数符合条件的区间没有和先前符合条件的区间有交集
//缩小区间
l=max(l,L);
r=min(r,R);
l=max(l,0.0);
l=min(l,1000.0);
r=max(r,0.0);
r=min(r,1000.0);
}
for(i=1;i<=one;i++)//再判断一次函数
{
L=f(0,K[i],BB[i],l);//找到区间左右改函数的值
R=f(0,K[i],BB[i],r);
if(L>mid&&R>mid)return 0;//如果都大于就不成立
//缩小区间
if(L<mid&&R>mid)r=get_one(K[i],BB[i],mid);
if(L>mid&&R<mid)l=get_one(K[i],BB[i],mid);
}
return 1;//符合条件
}
void QAQ()
{
int n=read(),i,j,k,one=0,two=0;
double Min=-1e12,Max=1e12,l,r,a,b,c;
for(i=1;i<=n;i++)
{
a=read();
b=read();
c=read();
if(a!=0)//二次函数和一次函数分开放
{
two++;
A[two]=a;
B[two]=b;
C[two]=c;
Min=max(Min,Top(a,b,c));
Max=max(Max,max(f(a,b,c,0),f(a,b,c,1000)));//找二分区间
}
else
{
one++;
K[one]=b;
BB[one]=c;
}
}
r=Max;
l=Min;
double mid,ans;
while(r-l>SPJ)//二分
{
mid=(l+r)/2;
if(check(mid,one,two)){r=mid-SPJ;/*结果可能更小,所以r减小*/ans=mid;/*记录下ans*/}else l=mid+SPJ;
}
printf("%.4lf\n",ans);//输出ans,保留4位小数
}
int i,t;
int main()
{
t=read();
for(i=1;i<=t;i++)//执行t遍操作
{
QAQ();
}
return 0;
}
二分太明显了…