1. 程式人生 > >#LOJ10013 曲線(三分)

#LOJ10013 曲線(三分)

題目連結

題目描述

明明做作業的時候遇到了 n 個二次函式 Si(x)=ax^2 + bx + c,他突發奇想設計了一個新的函式 F(x)=max⁡{Si(x)},i=1…n。

明明現在想求這個函式在 [0,1000] 的最小值,要求精確到小數點後四位,四捨五入。

輸入格式

輸入包含 T 組資料,每組第一行一個整數n;

接下來 n 行,每行 3 個整數 a, b, c ,用來表示每個二次函式的 3 個係數。注意:二次函式有可能退化成一次。

輸出格式

每組資料輸出一行,表示新函式 F(x) 的在區間 [0,1000] 上的最小值。精確到小數點後四位,四捨五入。

樣例

樣例輸入

2
1
2 0 0
2
2 0 0
2 -4 2

樣例輸出

0.0000
0.5000

資料範圍與提示

對於 50% 的資料,1≤n≤100;

對於 100% 的資料,1≤T≤10, 1≤n≤10^5, 0≤a≤100, 0≤∣b∣≤5000, 0≤∣c∣≤5000。

三分法適用於求解凸性函式的極值問題,二次函式就是一個典型的單峰函式。

設當前求解的區間為[l,r],令m_1=l+\frac{r-l}{3}m_2=r-\frac{r-l}{3},接著計算這兩個點的函式值f(m_1)f(m_2)之後將兩點中函式值更優的那個點稱為“好點”(若計算最小值,則f更小的那個點就為好點),而函式值較差的那個點稱為“壞點”。

可以利用反證法證明:最優點和好點在壞點的同側

如圖,f(m2)>f(m1),最大值為最優點,因此m1是壞點,m2是好點,最優點與m2會位於m1同側,求解區間由[l,r]變為[m1,r]。由此可以不停縮小求解區間,直至得出近似解。

如果函式不嚴格單調,三分法將不適用。

由於a大於等於0,函式S是開口向上的二次函式(當a=0時是一次函式),即S為先單調遞減,後單調遞增的下凸函式,或者是一次單調函式,F(x)=max⁡{Si(x)}也滿足單調性。因此可用三分法求解[0,1000]內的最小值。

AC程式碼:

////CSDN部落格:https://blog.csdn.net/qq_40889820
#include<iostream>
#include<sstream>
#include<algorithm>
#include<string>
#include<cstring>
#include<iomanip>
#include<vector>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<map>
#define mem(a,b) memset(a,b,sizeof(a))
#define e 2.71828182
#define Pi 3.141592654
#define INF 1<<30
using namespace std;
struct node
{
    double a,b,c;	
}quad[10010];
int n;
double fun(double x)//F(x)
{
	double ans=-INF;
	for(int i=1;i<=n;i++)
	ans=max(ans,quad[i].a*x*x+quad[i].b*x+quad[i].c);
	return ans;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL);cout.tie(NULL);
	int T;
	cin>>T;
	while(T--)
	{
		cin>>n;
		for(int i=1;i<=n;i++) cin>>quad[i].a>>quad[i].b>>quad[i].c;
		double l=0.0,r=1000.0;//[0,1000] 
		while(r-l>1e-11)
		{
			double mid1=l+(r-l)/3,mid2=r-(r-l)/3;
			if(fun(mid1)<fun(mid2))//mid1是好點
			r=mid2; 
			else l=mid1;
		}
		cout<<setiosflags(ios::fixed)<<setprecision(4)<<fun(l)<<endl;
	}
}