首先来说说三分的概念:
二分是把区间分为长度相等的两段,三分则是把区间分为长度相等的三段,进行查找,这样的查找称为三分查找,三分查找通
常用来迅速确定最值。
众所周知,二分算法的要求是搜索的序列是单调序列,而三分法所面向的搜索序列的要求是:序列为一个凸性函数。
与二分法类似,三分算法先把区间分为长度相等的三段,那么l与r之间就有两个点,分别是:ll=l+(r-l)/3=(2l+r)/3和
rr=r-(r-l)/3=(l+2r)/3
如果ll比rr更靠近最值,我们就舍弃右区间,否则我们舍弃左区间。
算法的正确性:
1、ll与rr在最值的同一侧。由于凸性函数在最大值(最小值)任意一侧都具有单调性,因此,ll与rr中,更大
(小)的那个数自然更为靠近最值。此时,我们远离最值的那个区间不可能包含最值,因此可以舍弃。
2、ll与rr在最值的两侧。由于最值在中间的一个区间,因此我们舍弃一个区间后,并不会影响到最值。
典型题目:HDU4355,HDU2438,POJ3301
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <math.h>
using namespace std;
const double EPS=1e-4;
const int N=50010;
double p[N],w[N];
int n;
double equ(double x)
{
double ans=0;
for(int i=0;i<n;i++)
{
double S=fabs(p[i]-x);
ans+=w[i]*S*S*S;
}
return ans;
}
double ternarySearch(double l,double r)
{
while(r-l>EPS)
{
double ll=(2*l+r)/3;
double rr=(l+2*r)/3;
double ans1=equ(ll);
double ans2=equ(rr);
if(ans1>ans2)
l=ll;
else
r=rr;
}
return l;
}
int main()
{
int T,i;
scanf("%d",&T);
for(int t=1;t<=T;t++)
{
scanf("%d",&n);
double l=1000000;
double r=-1000000;
for(i=0;i<n;i++)
{
scanf("%lf%lf",&p[i],&w[i]);
if(p[i]<l) l=p[i];
if(p[i]>r) r=p[i];
}
double tmp=ternarySearch(l,r);
printf("Case #%d: %.0lf\n",t,equ(tmp));
}
return 0;
}
题意:给定一个直角弯道的两条道路的宽度,然后再给出汽车的长度与宽度,问汽车能否通过该弯道?
如下图:
要使汽车能转过此弯道,那么就是汽车的左边尽量贴着那个直角点,而汽车的右下后方的点尽量贴着最下面的边。
我们以O点为原点建立直角坐标系,我们可以根据角a给出P点横坐标的函数F(a)
那么很容易得到:
其中有条件:,可以很容易证明是一个单峰函数,所以接下来就是三分了,如果的最大值小于等于
y,那么就能通过此直角弯道,否则就通不过。
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <math.h>
using namespace std;
const double EPS=1e-8;
double x,y,l,w;
double equ(double t)
{
return l*cos(t)+(w-x*cos(t))/sin(t);
}
double ternarySearch(double l,double r)
{
while(r-l>EPS)
{
double ll=(2*l+r)/3;
double rr=(l+2*r)/3;
double ans1=equ(ll);
double ans2=equ(rr);
if(ans1<ans2)
l=ll;
else
r=rr;
}
return l;
}
int main()
{
while(~scanf("%lf%lf%lf%lf",&x,&y,&l,&w))
{
double low=0,high=acos(-1.0)/2;
double tmp=ternarySearch(low,high);
if(equ(tmp)<=y) puts("yes");
else puts("no");
}
return 0;
}
题意:平面上给定n个点的坐标,求出包含这n个点的最小的正方形的面积。
分析:我们先找出一个边长都平行于坐标轴的最小的正方形,然后我们只需要在0到90度的所有点旋转过程中,每次旋转一点就
比较,然后记录边长最小的正方形,而在这个旋转过程中正方形的边长关于旋转角度的函数是一个单峰函数,所以可以三分。
这里要知道坐标旋转公式:
如果为旋转前的坐标,为旋转后的坐标,那么有:
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <math.h>
using namespace std;
const double EPS=1e-12;
const double PI=acos(-1.0);
const double INF=1e6;
const int N=35;
double x[N],y[N];
int n;
double Tri(double t)
{
double tx,ty;
double bx,by,sx,sy;
bx=by=-INF;
sx=sy=INF;
for(int i=0; i<n; i++)
{
tx=x[i]*cos(t)-y[i]*sin(t);
ty=x[i]*sin(t)+y[i]*cos(t);
//旋转坐标系,求出旋转后的点应该在什么位置
bx=max(tx,bx);
sx=min(tx,sx);
by=max(ty,by);
sy=min(ty,sy);
}
return max(bx-sx,by-sy);
}
double ternarySearch(double l,double r)
{
while(r-l>EPS)
{
double ll=(2*l+r)/3;
double rr=(l+2*r)/3;
double ans1=Tri(ll);
double ans2=Tri(rr);
if(ans1>ans2)
l=ll;
else
r=rr;
}
return l;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%lf%lf",&x[i],&y[i]);
double low=0,high=PI/2;
double tmp=ternarySearch(low,high);
printf("%.2lf\n",Tri(tmp)*Tri(tmp));
}
return 0;
}