题目提交地址:https://cn.vjudge.net/problem/UVA-1331
##第一篇题解##
最优三角剖分的一类题目都是差不多的。给你一个多边形,让你把它分割成若干个三角形,求三角形某最优解,比如UVA1331要求面积最大的三角形的面积最小。如图是各种切割方法:
不知道一开始看到最大值最小化会不会又一下子想到枚举答案二分去了呢,不过本题正解是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,其他部分和凸多边形的情形完全一样。
代码如下:
#include<cstdio>
#include<cstdlib>
#define INF 0xfffffff
const int Maxm=50+5;
int m;
int x[Maxm],y[Maxm];
double d[Maxm][Maxm];
double area(int a,int b,int c)
{
double s=(double)(1.0/2)*(x[a]*y[b]+x[b]*y[c]+x[c]*y[a]-x[a]*y[c]-x[b]*y[a]-x[c]*y[b]);
if(s<0) return -s;
return s;
}
double mymin(double a,double b) {return a<b?a:b;}
double mymax(double a,double b) {return a>b?a:b;}
bool check(int a,int b,int c)
{
int i;
for(i=1;i<=m;i++)
{
if(i==a||i==b||i==c) continue;
double d=area(a,b,i)+area(a,c,i)+area(b,c,i)-area(a,b,c);
if(d<0) d=-d;
if(d<=0.01) return 0;
}
return 1;
}
int main()
{
int n;
scanf("%d",&n);
while(n--)
{
int i,j,k;
scanf("%d",&m);
for(i=1;i<=m;i++) scanf("%d%d",&x[i],&y[i]);
for(i=m;i>=1;i--)
{
d[i][i+1]=0.0;
for(j=i+2;j<=m;j++)
{
d[i][j]=INF;
for(k=i+1;k<j;k++)
{
if(check(i,j,k))
d[i][j]=mymin(d[i][j],mymax(mymax(area(i,j,k),d[i][k]),d[k][j]));
}
}
}
printf("%.1lf\n",d[1][m]);
}
}
##第二篇题解##
博客地址: http://blog.csdn.net/c20190102/article/details/75418824
将一个多边形用它不相交的对角线将它分成若干个三角形,使得最大的三角形面积最小,求最大三角形的面积。如图是一个六边形的几种剖分:
思路
记由点u,u+1,…,v-1,v(u< v)组成的多边形为 F(i,j)
首先,总的多边形为
F(1,N)
,
对于
F(1,N)
的一个子多边形
F(i,j)(1<=i<j<=N)
,可以在点i+1-点j-1中任意找到一个点k,将其分割为:
Δijk,F(i,k),F(j,k)
,如图:
设d[i][j]表示 F(i,j) 中的最优解,很显然,先要找到 max{SΔijk,d[i][k],d[k][j]} ( SΔijk 为 Δijk 的面积),也就是找到以k为分割点, F(i,j) 中的最大的三角形的面积,然后枚举k点,使这个最大的三角形面积最小即可。
状态转移方程: d[i][j]=min{max{SΔijk,d[i][k],d[k][j]}}(i<k<j)
这样就完了吗?不是的,还有一个问题:
Δijk
合法吗?例如这样的“分割”:
当 k 点在如图位置时, Δijk 显然不合法,因为线段 ik 不是 F(1,7) 的对角线!
所以怎么判断
Δijk
是否合法呢?
换个思路,判断
Δijk
是否合法实际上就是看
Δijk
内有没有其他的点。然后枚举除ijk外每一个点呗,再看这点是不是在
Δijk
内。
怎么判断点是否在
Δijk
内呢?
“面积法”是很好的办法:设现在枚举到的点为
x
,计算
T=SΔijx,SΔikx,SΔkjx
,再把它们加起来,如果
x
不在
Δijk
,显然
T==SΔijk
,所以判断
T
与
SΔijk
的大小关系即可。不明白的可以画画图,我懒得再画了……
#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));
}
}