UVA1331 题解

前言

题目传送门!

更好的阅读体验?

第一次写黑题题解

计算几何、区间 \(\text{DP}\)

思路

我们可以把大多边形分拆成小的多边形来看,并且小的多边形的顶点,在大多边形的顶点的编号是连续的。

所以考虑区间 \(\text{DP}\)。设 \(dp_{i, j}\) 表示用 \([i, j]\) 之间的所有格点组成的小多边形,怎样能使答案最小。

最终结果显然就是 \(dp_{1, n}\)

考虑如何转移。对于 \(dp_{i, j}\),找一个断点将这个多边形一分为二,并取三个区域的答案最大值。

简单的说,转移方程就是:\(dp_{i, j} = \min\limits_{l < k < r}\Big\{\max\{dp_{i, k}, dp_{k, j}, S\triangle a_i a_k a_j\}\Big\}\)

\(S\triangle a_i a_k a_j\),可以用叉积或者海伦公式算。

但是请注意,并不是所有 \(k\) 都能转移的,所以这样写不完全正确。

比如说下图的情况就是不合法的转移:

显然,三角形超出了多边形的位置。因此,我们要判断这个三角形是否在多边形内

也就是说,大多边形的每个点(除了三角形的这三个点)都不能在三角形内部。

这一点还是比较好判断的:如果 \(S\triangle a_x a_j a_k + S\triangle a_i a_x a_k + S\triangle a_i a_j a_x = S\triangle a_i a_j a_k\),说明 \(x\) 点在 \(\triangle a_i a_j a_k\) 内。

这个公式是什么意思呢?举例来说,合法情况就是下图:

那么这题就完美做完啦!

完整代码

本题精度诡异,我的代码 \(10^{-8}\)\(10^{-9}\) 都过不了,但是设成 \(10^{-5}\) 可以过。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
using namespace std;

typedef double db;
typedef pair <int, int> pii;
#define x first
#define y second
const int N = 55;

int n;
pii a[N];
namespace calc {
    db dis(int i, int j) //a[i] 到 a[j] 的距离
    {
        db dx = a[i].x - a[j].x, dy = a[i].y - a[j].y;
        return sqrt(dx * dx + dy * dy);
    }
    db area(int i, int j, int k) //三角形 a[i],a[j],a[k] 的面积
    {
        db a = dis(i, j), b = dis(i, k), c = dis(j, k);
        db s = (a + b + c) / 2;
        return sqrt(s * (s - a) * (s - b) * (s - c));
    }
    bool all_out_triangle(int a, int b, int c) //是否其余的全部点均在 三角形a[i],a[j],a[k] 外面
    {
        for (int i = 1; i <= n; i++)
            if (i != a && i != b && i != c)
            {
                db x = area(a, b, c);
                db y = area(a, b, i) + area(a, i, c) + area(i, b, c);
                if (fabs(x - y) < 1e-5) return false; //即:x不等于y。这样写是防止精度丢失
            }
        return true;
    }
};

db dp[N][N];
db MAX(db x, db y, db z) {return max(x, max(y, z));} //取 max(x, y, z)
void solve()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d%d", &a[i].x, &a[i].y);

    memset(dp, 127, sizeof dp); //double 初始化要用 127 而不是 0x3f
    for (int i = 1; i < n; i++) dp[i][i + 1] = 0; //只有两个点
    for (int i = 1; i <= n - 2; i++) dp[i][i + 2] = calc::area(i, i + 1, i + 2); //有三个点,答案就是这个三角形的面积

    for (int len = 3; len <= n; len++) //区间 DP
        for (int i = 1, j = len; j <= n; i++, j++)
            for (int k = i + 1; k < j; k++)
                if (calc::all_out_triangle(i, j, k))
                    dp[i][j] = min(dp[i][j], MAX(dp[i][k], dp[k][j], calc::area(i, j, k)) );
    printf("%.1lf\n", dp[1][n]);
}
int main()
{
    ios::sync_with_stdio(false);
    int T;
    scanf("%d", &T);
    while (T--) solve();
    return 0;
}

希望能帮助到大家!

posted @ 2022-11-17 08:38  liangbowen  阅读(69)  评论(1编辑  收藏  举报