ZOJ 3537 Cake 【区间DP + 凸多边形三角剖分】

文章讲述了如何利用Andrew算法判断多边形是否凸,然后通过动态规划(dp)求解在保证凸性条件下,最少花费将多边形分割成三角形的方法,计算每条边的连接成本并进行转移更新。
摘要由CSDN通过智能技术生成

Cake

1

题意

给定平面坐标上的 n n n 个点,如果是凸多边形的话,就用最少的花费把这个多边形剖分成若干个三角形,剖分的线段端点只能是原多边形的顶点,一条线段的花费为: ∣ x i + x j ∣ × ∣ y i + y j ∣ m o d p |x_i + x_j| \times |y _i + y_j| mod p xi+xj×yi+yjmodp

思路

首先我们使用 A n d r e w Andrew Andrew 算法判断一下是否为凸包。对于当前的顶点 [ 0 , n − 1 ] [0, n - 1] [0,n1],我们先看一条边: 0 ↔ n − 1 0 \lrarr n - 1 0n1,这条边肯定属于某个三角形,我们肯定要有一条线的端点之一是 0 0 0,另外一条线的端点之一是 n − 1 n - 1 n1,并且这两条线有一个公共端点 k ( 1 ≤ k ≤ n − 2 ) k(1 \leq k \leq n - 2) k(1kn2),因为只有这样,才能将这条边剖分成属于某个三角形,而那两条线段就是三角形的两条边,第三条边就是 0 ↔ n − 1 0 \lrarr n - 1 0n1

那么这两条线就将我们的 [ 0 , n − 1 ] [0, n - 1] [0,n1] 区间的端点分成了两个部分: [ 0 , k ] [0, k] [0,k] [ k + 1 , n − 1 ] [k + 1, n - 1] [k+1,n1],这是两个子状态,我们可以使用 d p [ l ] [ r ] dp[l][r] dp[l][r] 表示区间 [ l , r ] [l,r] [l,r] 的三角剖分的最小花费

那么转移就可以枚举区间的分裂点 k k k

  • d p [ l ] [ k ] = m i n l + 1 ≤ k ≤ r − 1 ( d p [ l ] [ k ] + d p [ k + 1 ] [ r ] + c o s t ( l , k ) + c o s t ( k , r ) ) dp[l][k] = min_{l + 1 \leq k \leq r - 1} (dp[l][k] + dp[k + 1][r] + cost(l, k) + cost(k, r)) dp[l][k]=minl+1kr1(dp[l][k]+dp[k+1][r]+cost(l,k)+cost(k,r)) c o s t ( l , k ) cost(l,k) cost(l,k) 表示连接 l l l k k k 的花费

注意如果 l + 1 ≤ r l + 1 \leq r l+1r 时, c o s t ( l , r ) = 0 cost(l, r) = 0 cost(l,r)=0,因为原本的多边形上已经有了这条边,不用而外花费去切割

跑区间 D P DP DP 的时候,我们从长度为 3 3 3 的区间开始,长度小于等于 2 2 2 的区间花费都为 0 0 0,都不用切割

时间复杂度: O ( n 3 ) O(n^3) O(n3)

#include<bits/stdc++.h>
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long
#define ALL(v) v.begin(), v.end()
#define Debug(x, ed) std::cerr << #x << " = " << x << ed;

const int INF=0x3f3f3f3f;
const long long INFLL=1e18;

typedef long long ll;

int mod;

struct Point{
    int x,y;

    Point(int xx=0,int yy=0){x=xx,y=yy;}

    Point operator + (Point B){ //向量 +
        return Point(x+B.x,y+B.y);
    }
	
    Point operator - (Point B){	//向量 -
        return Point(x-B.x,y-B.y);
    }

    Point operator * (int k){ //向量等比例放大
        return Point(k*x,k*y);
    }
    bool operator == (Point B){ //unique 用到
        return x-B.x == 0 && y-B.y == 0;
    }

    bool operator < (Point B){
        return x-B.x < 0 || (x-B.x == 0 && y-B.y < 0);
    }
};

int Cross(Point A,Point B){
    return A.x*B.y - A.y*B.x;
}

int Convex_hull(Point* p,int n,Point* ch){ //ch[]储存凸包顶点
    n = std::unique(p,p+n) - p;	//去重
    std::sort(p,p+n); //排序
    int v = 0;
    /* 求下凸包 */
    fore(i,0,n){
        while(v>1 && Cross(ch[v-1]-ch[v-2],p[i]-ch[v-2]) <= 0)
            --v;
        ch[v++] = p[i];
    }
    int j = v;
    /* 求上凸包 */
    for(int i=n-2;i>=0;--i){
        while(v>j && Cross(ch[v-1]-ch[v-2],p[i]-ch[v-2]) <= 0)
            --v;
        ch[v++] = p[i];
    }
    if(n>1)	--v; //p[0]被加入了两次
    return v; //返回凸包顶点数
}

Point p[500];
Point ch[500];

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int n;
    while(std::cin >> n >> mod){
        fore(i, 0, n) std::cin >> p[i].x >> p[i].y;
        
        if(Convex_hull(p, n, ch) < n){ //不是凸包
            std::cout << "I can't cut.\n";
            continue;
        }

        if(n <= 3){
            std::cout << "0\n";
            continue;
        }

        std::vector<std::vector<int>> dp(n + 5, std::vector<int>(n + 5, INF));
        std::vector<std::vector<int>> dis(n + 5, std::vector<int>(n + 5, 0)); //连接两个顶点的cost

        auto cal = [&](int i, int j) { //计算cost
            return std::abs(ch[i].x + ch[j].x) * std::abs(ch[i].y + ch[j].y) % mod;
        };

        fore(i, 0, n)
            fore(j, i + 2, n)
                dis[i][j] = dis[j][i] = cal(i, j); //预计算cost
        
        fore(i, 0, n) dp[i][i + 1] = 0;
        
        fore(len, 3, n + 1)
            fore(L, 0, n - len + 1){
                int R = L + len - 1;
                fore(k, L + 1, R)
                    dp[L][R] = std::min(dp[L][R], dp[L][k] + dp[k][R] + dis[L][k] + dis[k][R]);
            }
        std::cout << dp[0][n - 1] << endl;
    }
    return 0;
}
  • 20
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值