UVa 1331 最大面积最小的三角剖分(dp)
一共有4条边,我们可以以随意的顺序切割,不过如果这样的话,就会出现类似v0v3v4v6这样的多边形,这样的多边形很难用简洁的状态表示出来,这就是让我一开始很纠结的地方。
其实我们会发现,对于同一种切割方法,我们可以有多种切割顺序,但我们只要计算一种就好了,不如把决策顺序规范化。例如还是上面第一张图,我们可以先切割出三角形v0v3v6,那么我们切割完之后可以把图分割成一个三角形和两个多边形,而组成这两个个多边形的点的编号都是连续的。
于是我们可以得出一种切割方法,比如我要切割多边形i,i+1,…,j-1,j(i < j),那么我下一步切割的三角形一定有i和j这两个点(这样的规定并不会出错,因为无论我们如何切割,分出来的三角形中一定有一个过i和j),而这样的切割方法的优点是保证了每次切出来的多边形组成的点的编号都是连续的,于是我们就可以用两个元素表示一个多边形的状态了,这样dp转移方程很容易可以得出来:
d(i,j)=min{max(d(i,k),d(k,j),S(i,j,k))|i < k< j};
其中,S(i,j,k)为三角形i-j-k的面积。不过此时需要保证i-j是对角线(唯一的例外是i=0且j=n-1),具体做法是当边i-j不满足条件时直接设为INF,其他部分和凸多边形的情形完全一样。
这里用海伦公式计算三角形面积
用面积差判断三角形内部是否有点
明确了这些进行dp
最后答案在d[1][N]中
d[i][j]表示的是以i,j为边界不包含序号1的点的多边形里最大三角形面积最小值
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAXN 50
#define INF 0x7fffffff
#define eps 0.0001
struct point
{
double x,y;
}p[MAXN+5];//点
int N;
double d[MAXN+5][MAXN+5];//动归
double dis(int x,int y){return sqrt((p[x].x-p[y].x)*(p[x].x-p[y].x)+(p[x].y-p[y].y)*(p[x].y-p[y].y));}//计算两点距离
double area(int x,int y,int z)//计算由点x,y,z构成的三角形的面积(海伦公式)
{
double a=dis(x,y),b=dis(y,z),c=dis(x,z);
double p=(a+b+c)/2;
return sqrt(p*(p-a)*(p-b)*(p-c));
}
bool check(int x,int y,int z)//判断由点x,y,z构成的三角形中有没有点
{
double tarea=area(x,y,z);
for(int i=1;i<=N;i++)
{
if(i==x||i==y||i==z) continue;
double a=area(i,x,y),b=area(i,y,z),c=area(i,x,z);
if(fabs(a+b+c-tarea)<eps)//double计算有精度误差,不要用==
return 0;
}
return 1;
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d",&N);
for(int i=1;i<=N;i++)
scanf("%lf%lf",&p[i].x,&p[i].y);
for(int i=N-2;i>=1;i--)//从大到小选取第一个点
for(int j=i+2;j<=N;j++)//选择第二个点
{
d[i][j]=INF;
for(int k=i+1;k<j;k++)//选择两点中间分割点
if(check(i,j,k))
d[i][j]=min(d[i][j],max(area(i,j,k),max(d[i][k],d[k][j])));
}
printf("%.1lf\n",d[1][N]);
memset(d,0,sizeof(d));
}
}