最近在練習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