【題目描述】
明明做作業的時候遇到了n個二次函數Si(x)= ax2 + bx + c,他突發奇想設計了一個新的函數F(x) = max(Si(x)), i = 1...n.
明明現在想求這個函數在[0,1000]的最小值,要求精確到小數點後四位四捨五入。
【輸入數據】
輸入包含T 組數據 (T < 10) ,每組第一行一個整數 n(n ≤ 10000) ,之後n行,每行3個整數a (0 ≤ a ≤ 100), b (|b| ≤ 5000), c (|c| ≤ 5000) ,用來表示每個二次函數的3個係數,注意二次函數有可能退化成一次。
【輸出數據】
每組數據一個輸出,表示新函數F(x)的在區間[0,1000]上的最小值。精確到小數點後四位,四捨五入。
【樣例輸入】
2
1
2 0 0
2
2 0 0
2 -4 2
【樣例輸出】
0.0000
0.5000
【數據範圍】
T < 10, n ≤ 10000 , 0 ≤ a ≤ 100,|b| ≤ 5000, |c| ≤ 5000
前50%數據n ≤ 100
好吧 一開始想的是 F(x)肯定在某兩個函數的交點處改變單調性
然後暴力枚舉交點 O(n)驗證是否爲所有函數圖象中最上面的的那個(在F(x)上)
然後事實證明可以過50分
但是自己沒寫啊啊啊啊啊啊啊啊啊
用隨機寫的 撒點 隨機移動
結果一個點也沒過!!!
下午重新改了改 突然發現
數據範圍中 a 沒加絕對值!!!
這說明什麼呢?
說明F(x)是一個向下凸的單峯函數!!!
然後就可以爬山算法啦
嫌爬山麻煩 就寫了個三分。。。
話說對精度要求太高了 要1e-9纔可過
估計是有的函數太“陡”了吧
喪心病狂!!!還好不是noip
#include<iostream>
#include<cstdio>
#include<cstring>
#include<iomanip>
using namespace std;
double l,r,mid,midmid;
double ymid,ymidmid;
struct self{double x,y,w;}s[10011];
int m,a,b,c,d;
int casenum;
double f(int i,double x)
{
return s[i].x*x*x+s[i].y*x+s[i].w;
}
double finduper(double i)
{
double ret=-999999999;
for(int a=1;a<=m;a++)
ret=max(ret,f(a,i));
return ret;
}
int main()
{
freopen("curves.in","r",stdin);
freopen("curves.out","w",stdout);
scanf("%d",&casenum);
while(casenum)
{
casenum--;
scanf("%d",&m);
for(a=1;a<=m;a++)scanf("%lf%lf%lf",&s[a].x,&s[a].y,&s[a].w);
l=0;r=1000;
while(l<r-1e-9)
{
mid=(l+r)/2;
midmid=(mid+r)/2;
ymid=finduper(mid);
ymidmid=finduper(midmid);
if(ymid<ymidmid)r=midmid;
else l=mid;
}
cout<<fixed<<setprecision(4)<<ymid<<endl;
}
return 0;
}
另外附上二分的題解
本題的關鍵是二分答案。由於輸出只要求精確到4位,我們可以二分結果。設結果爲ans,對於二分得到的任何一個值mid,我們可以計算出每條二次函數值低於它的區間。如果mid < ans,那麼剛纔所說的那些區間的交集爲空,如果mid > ans 那麼剛纔所說的那些區間的交集就不爲空了。
所以現在我們只需要進行二分搜索答案ans,每次通過把數帶入二次函數計算出每個二次函數所對應的區間,然後判斷這些區間的交集是否爲空即可。判斷區間交集是否爲空可以直接判斷他們最左的右端點是否在最右的左端點右邊即可。每次二分判斷的時間複雜度爲O(n),整個算法的時間複雜度爲O(pn), p爲二分的次數。