最近在练习DP专题,学会了很多表示方法和转换方法,今天做最优三角剖分的时候发现脑子卡了,不会表示状态,于是写个博客记录一下。
最优三角剖分的一类题目都是差不多的。给你一个多边形,让你把它分割成若干个三角形,求三角形某最优解,比如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,其他部分和凸多边形的情形完全一样。
代码如下:
1 #include<cstdio> 2 #include<cstdlib> 3 #define INF 0xfffffff 4 5 const int Maxm=50+5; 6 7 int m; 8 int x[Maxm],y[Maxm]; 9 double d[Maxm][Maxm]; 10 11 double area(int a,int b,int c) 12 { 13 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]); 14 if(s<0) return -s; 15 return s; 16 } 17 18 double mymin(double a,double b) {return a<b?a:b;} 19 20 double mymax(double a,double b) {return a>b?a:b;} 21 22 bool check(int a,int b,int c) 23 { 24 int i; 25 for(i=1;i<=m;i++) 26 { 27 if(i==a||i==b||i==c) continue; 28 double d=area(a,b,i)+area(a,c,i)+area(b,c,i)-area(a,b,c); 29 if(d<0) d=-d; 30 if(d<=0.01) return 0; 31 } 32 return 1; 33 } 34 35 int main() 36 { 37 int n; 38 scanf("%d",&n); 39 while(n--) 40 { 41 int i,j,k; 42 scanf("%d",&m); 43 for(i=1;i<=m;i++) scanf("%d%d",&x[i],&y[i]); 44 for(i=m;i>=1;i--) 45 { 46 d[i][i+1]=0.0; 47 for(j=i+2;j<=m;j++) 48 { 49 d[i][j]=INF; 50 for(k=i+1;k<j;k++) 51 { 52 if(check(i,j,k)) 53 d[i][j]=mymin(d[i][j],mymax(mymax(area(i,j,k),d[i][k]),d[k][j])); 54 } 55 } 56 57 } 58 printf("%.1lf\n",d[1][m]); 59 } 60 }
ps:给定三个点求面积最好用叉积,而如果知道三条边求面积用海伦公式。
20:19:34