区间DP_最优三角剖分

最优三角剖分有很多种类型,如:求让所有三角形权值和最大的方案、所有三角形权值和最小的方案、所有三角形最大三角形面积最小的方案........

但是不论怎么变化,他们的解题思路都是相同的,只是状态转移方程有些许的差别而已,首先我们先看第三种情况.......

栗子:UVA 1331

本题主要思路以及图片多参考自博客:https://blog.csdn.net/c20190102/article/details/75418824

在一个任意多边形中进行三角形剖分,求使得三角形中最大三角形面积最小的面积

假设在一个凸多边形中,设dp[i][j] 表示在{vi,vi+1, ...... ,vj}点中最大的剖分出三角形的最大面积,假设在位置k处(i<k<j)取得面积最大的最小值,那么我们就可以表示为:

(图片来自于:https://blog.csdn.net/c20190102/article/details/75418824

那么我们根据k点将面积分成了三个区域,分别为 △ijk 、F(i,k)  、 F(k,j),那么我们只需要找出这三个面积中最大值,然后和现在dp[i][j]的值去一个较小的值,就是当前区域中的答案,我们还会发现,F(k,j) 和 F(i,k)的大小在我们之前已经计算过了,那么这就是我们的区间DP的最优子结构,所以我们可以得到状态转移方程:

dp[i][j] = min( dp[i][j] , max(S△ijk  , max(F(i,k) , F(k,j) ) ) )  

那么当我们的多边形不是凸多边形怎么办呢?

如果不是凸多边形的话,我们的划分三角形时,我们要判断这条线是否在三角内部


(图片来自于:https://blog.csdn.net/c20190102/article/details/75418824

像上面的2-5之间的连线就不在多边形内部,这种情况的话,就一定会有一个点在三角形内部,那么我们判断是否符合情况的话,我们可以遍历每一个点,然后计算这个点和三条边产生三个三角形的面积和,如果等于S△ijk,那么就在三角形内部,不等于,就不在三角形内部,

剩下的就是区间DP的内容了

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <cmath>
#define INF 0x3f3f3f3f
#define eps 0.0001

using namespace std;//因为不确定是否为凸多边形,所以要判断三角形是否符合情况
const int MAXN = 55;
//海伦公式: p = (a+b+c)/2   S = sqrt(p*(p-a)*(p-b)*(p-c))
struct Point
{
    double x,y;
}p[MAXN];
int N;
double dp[MAXN][MAXN];//表示在区间[i,j]中的面积最大值

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)//根据海伦公式得到三角形的面积
{
    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)//判断这个面积是否合法,也就是判断是否有其他的点在三角形内部,如果有那么就不合法,没有就是合法的
{
    //如果这个点在三角形内部,那么它与三条边产生的三个三角形面积和叫起来近似等以大三角形的面积、所以这样就可以判断是否在三角形内部
    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)
            return 0;
    }
    return 1;
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        memset(dp,0,sizeof(dp));
        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 --)//区间DP,为什么要从后向前枚举?因为我们每一次更新状态的时候,用到的都是后面的所有状态
            //如果从前向后,那么最后一个状态不会是已经更新的,那么就必须从后向前更新!
            for(int j = i+2;j <= N;j ++)
            {
                dp[i][j] = INF;
                for(int k = i+1;k <j;k ++)
                    if(check(i,j,k))
                        dp[i][j] = min(dp[i][j],max(area(i,j,k),max(dp[i][k],dp[k][j])));
            }
        printf("%.1lf\n",dp[1][N]);
    }
    return 0;
}

然后是所有三角形权值最小或者最大的三角形剖分的计算,只看计算权值最小的情况,我们还是和上面相同,只是状态转移方程发生了变化,

dp[i][j] = min( dp[i][j] , dp[i][k] + dp[k][j]  + Get_weight(i , j, k))

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <cmath>
#define INF 0x3f3f3f3f
#define eps 0.0001

using namespace std;
const int N = 55;
int dp[N][N];
int weight[N][N];
int n;
int Get_weight(int x,int y,int z)
{
    return weight[x][y] + weight[y][z] + weight[z][x];
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        memset(weight,0,sizeof(weight));
        memset(dp,0,sizeof(dp));
        scanf("%d",&n);

        for(int i =1 ;i <= n;i++)
            for(int j = 1;j <= n;j ++)
                scanf("%d",&weight[i][j]);
        for(int i = n-2;i >= 1;i --)
        {
            for(int j = i+2;j <= n;j ++)
            {
                dp[i][j] = INF;
                for(int k = i+1;k < j;k ++)
                    dp[i][j] = min(dp[i][j] , dp[i][k] + dp[k][j] + Get_weight(i,k,j));
            }
        }
        printf("%d\n",dp[1][n]);
    }
}
(只是写出来了,没有验证正确性,如果有错误,欢迎大家指正,谢谢!)

参考博客

https://blog.csdn.net/c20190102/article/details/75418824

猜你喜欢

转载自blog.csdn.net/li1615882553/article/details/80169803