您的位置:首页 > 其它

【UVA1331】关于最优三角剖分

2017-11-03 15:45 363 查看


题目提交地址: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+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]);
}
}


[b]##第二篇题解##

博客地址: 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]
);
memset(d,0,sizeof(d));
}
}



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: