ZOJ - 3735 Cake(凸包,区间dp,最优三角剖分)

对着kuangbin博客开始刷区间dp!

按照题目的要求先用Graham扫描法求出凸包,如果凸包上点的数目正好等于输入点的数目,也就说明输入的点恰好构成凸包,否则输出“I can't cut.”。

求出凸包后,注意到这是一个环,所以只要把环剪开,就可以用区间dp做了。

定义状态dp[i][j]表示i到j这段区间内最优三角剖分的代价,注意此时i和j之间已经有边了。

所以状态转移方程为:dp[i][j] = min(dp[i][k] + dp[k][j] + cost[i][k] + cost[j][k]) (i < k < j)

至于为什么要算两个代价呢,不是一次只切一刀吗?

其实这是由我们定义的状态决定的,在定义的状态中,dp[i][j]默认i和j已经有边了,如果我们只切i-k这一刀,而不切k-j这一刀,那么子状态dp[k][j]就不符合定义,因为k和j之间没有边。

代码如下:

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <cmath>

using namespace std;

const int INF = 0x3f3f3f3f;
struct Point
{
    int x, y;
};
Point p[500];
int dp[500][500];
Point stack[500];  // Graham扫描法要用到的栈
int top;  // 用来标记栈顶
int n, m;

// 计算两点间的欧式距离
double dist(Point p1, Point p2)
{
    return sqrt((double)(p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
}

// 计算向量(p1 - p0)叉乘向量(p2 - p0)的结果,符号为正,则方向向上。
int cross(Point p0, Point p1, Point p2)
{
    return (p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x);
}

bool cmp(Point p1, Point p2)
{
    int tmp = cross(p[0], p1, p2);
    if (tmp)
        return tmp > 0;
    return dist(p[0], p1) < dist(p[0], p2);
}

void Graham(int n)
{
    for (int i = 1; i < n; i++)
        if (p[i].y < p[0].y || (p[i].y == p[0].y && p[i].x < p[0].x))
            swap(p[i], p[0]);
    sort(p + 1, p + n, cmp);  // 以p0为原点的极角排序
    stack[0] = p[0];
    stack[1] = p[1];
    top = 2;
    for (int i = 2; i < n; i++)
    {
        while (top >= 2 && cross(stack[top - 2], stack[top - 1], p[i]) < 0)
            top--;
        stack[top++] = p[i];
    }
}

// 计算题目中定义的代价
int cost(int a, int b)
{
    if (abs(a - b) == 1)
        return 0;
    return abs(p[a].x + p[b].x) * abs(p[a].y + p[b].y) % m;
}

int main()
{
    //freopen("test.txt", "r", stdin);
    while (~scanf("%d%d", &n, &m))
    {
        for (int i = 0; i < n; i++)
            scanf("%d%d", &p[i].x, &p[i].y);
        Graham(n);
        if (top != n || n < 3)
        {
            printf("I can't cut.\n");
            continue;
        }
        memset(dp, 0, sizeof(dp));
        for (int len = 2; len < n; len++)
            for (int i = 0; i < n; i++)
            {
                int j = i + len;
                if (j >= n)
                    break;
                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] + cost(i, k) + cost(k, j));
            }
        printf("%d\n", dp[0][n - 1]);
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值