【区间dp】-----例题4【凸多边形的划分】

凸多边形的划分

题目描述

给定一个具有 N 个顶点的凸多边形,将顶点从 1 至 N 标号,每个顶点的权值都是一个正整数。
将这个凸多边形划分成 N-2 个互不相交的三角形,求所有这些三角形顶点权值乘积之和的最小值。


输入描述

  • 第一行输入一个整数 N,表示顶点个数。
  • 第二行输入 N 个整数,依次为顶点 1 至顶点 N 的权值。

输出描述

输出仅一行,为这些三角形顶点权值乘积和的最小值。


示例

输入

5
121 122 123 245 231

输出

12214884

备注

  • 对于 100% 的数据,有 N ≤ 50
  • 每个顶点的权值均小于 10^9

题解:

二、问题建模:为什么用区间 DP?

这个问题与经典的 矩阵链乘法石子合并 类似,都是典型的 区间动态规划(Interval DP) 问题。

我们希望找到一种划分方式,将整个凸多边形划分为若干个三角形,使得所有三角形的顶点权值乘积之和最小。

我们可以把原问题看作是从顶点 i 到顶点 j 构成的一个子多边形的最优划分问题,这样就可以使用 DP 来递推求解。


三、状态定义

dp[i][j] 表示从第 i 个顶点到第 j 个顶点所构成的子多边形(包含这两个顶点),划分成若干三角形后,其所有三角形顶点乘积和的最小值。

注意:这里的 ij 是连续编号的顶点,且顺序不变。


四、初始化条件

  • 当子区间的长度为 1 或 2 时,无法构成三角形,因此代价为 0:
for (int len = 1; len <= 2; len++) 
{
    for (int i = 1; i + len - 1 <= n; i++) 
    {
        int j = i + len - 1;
        dp[i][j] = 0;
    }
}
  • 其他情况的 dp[i][j] 初始化为一个非常大的值,表示尚未计算或不可达。

五、状态转移方程

对于任意一个子区间 [i, j],我们尝试枚举中间分割点 k (i < k < j),将 [i, j] 分割成两个子区间:

  • 左边:[i, k]
  • 右边:[k, j]

此时会形成一个三角形 (i, k, j),它的贡献是 w[i] * w[k] * w[j]

于是有状态转移:

dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j] + w[i] * w[k] * w[j]);

其中 k 的取值范围是 i < k < j


六、遍历顺序

我们按照子区间长度从小到大进行枚举:

// 区间长度从 3 开始
for (int len = 3; len <= n; ++len) 
{         
	// 枚举起点
    for (int i = 1; i + len - 1 <= n; ++i) 
    { 
    	// 计算终点
        int j = i + len - 1;                 
        // 执行状态转移。。。。。。
    }
}

这种顺序确保我们在计算 dp[i][j] 时,所依赖的 dp[i][k]dp[k][j] 已经被计算过。


七、数据类型选择

由于每个顶点的权值可以高达 1e9,而一个三角形的乘积可以达到 1e27,多个三角形的总和可能会超出 long long 的范围(约为 1e18)。因此我们使用 C++ 中的 __int128 类型来防止溢出。

同时,为了打印 __int128 类型的数值,需要自定义打印函数。


八、完整代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using lll = __int128;  // 定义 __int128 类型别名,方便使用

// __int128 的打印函数:
// 标准库不支持直接打印 __int128,因此需要自定义打印函数
void print128(lll x) {
    if (x == 0)  // 特判0
    {
        cout << '0';
        return;
    }
    
    if (x < 0)  // 处理负数,先打印负号,再取正
    {
        cout << '-';
        x = -x;
    }
    
    string s = "";
    // 将数字转换为字符串,取模得到最低位,累加字符
    while (x > 0) 
    {
        s += (x % 10 + '0');  // 取个位数字并转成字符
        x /= 10;              // 去掉个位数字,向高位进位
    }
    
    // 因为是从低位向高位依次加入字符,字符串是反向的,需反转
    reverse(s.begin(), s.end());
    cout << s;  // 输出字符串
}

int main()
{
    ll n;
    cin >> n;  // 输入顶点个数(多边形点数)

    vector<ll> w(n + 1, 0);  // 权值数组,1-based 索引,w[i] 为第 i 个顶点的权值
    for (ll i = 1; i <= n; i++)
    {
        cin >> w[i];
    }
    
    // 定义一个很大的数 N 作为无穷大(INF),用来初始化 DP 表
    // N = 2^(121),非常大,远大于可能出现的最大代价,防止溢出
    lll N = 2;
    for (ll i = 0; i < 120; i++)
    {
        N *= 2;
    }
    
    // 初始化 DP 数组,dp[i][j] 表示区间 [i,j] 的最小剖分代价
    // 一开始赋值为无穷大 N,表示暂时不可达或未计算
    vector<vector<lll>> dp(n + 1, vector<lll>(n + 1, N));
    
    // 区间长度为 1 或 2 的子区间不构成三角形,代价为 0
    // 所以这些区间 dp 值初始化为 0
    for (ll len = 1; len <= 2; len++)
    {
        for (ll i = 1; i + len - 1 <= n; i++)
        {
            ll j = i + len - 1;
            dp[i][j] = 0;
        }
    }
    
    // 区间 DP 主要部分:从长度 3 到 n 的区间逐步计算最小代价
    for (ll len = 3; len <= n; len++)
    {
        for (ll i = 1; i + len - 1 <= n; i++)
        {
            ll j = i + len - 1;
            
            // 尝试所有可能的中间分割点 k,将区间 [i,j] 分成 [i,k] 和 [k,j]
            for (ll k = i + 1; k < j; k++)
            {
                // 代价由三部分组成:
                // 左子区间最优代价 dp[i][k]
                // 右子区间最优代价 dp[k][j]
                // 当前剖分三角形代价 w[i] * w[k] * w[j]
                lll cost = dp[i][k] + dp[k][j] + (lll)w[i] * w[k] * w[j];
                
                // 取所有分割点中的最小代价
                dp[i][j] = min(dp[i][j], cost);
            }
        }
    }
    
    // 打印最终结果,dp[1][n] 即整个多边形的最小剖分代价
    print128(dp[1][n]);
    cout << "\n";

    return 0;
}

九、模拟构建 dp 表(以 n=5 为例)

假设权值数组为:

Index:   1     2     3     4     5
Value: 121   122   123   245   231

我们逐步填充 dp[i][j]

i \ j12345
100??最终结果
2N00??
3NN00?
4NNN00
5NNNN0

随着 DP 过程推进,逐步填入每个 dp[i][j] 的最小值,最终得到 dp[1][5] 即为答案。


十、常见问题 & 注意事项

问题解释
为什么要使用 __int128防止乘积溢出 long long 范围(1e18)
为什么不能贪心?每一步的选择会影响后续结构,必须全局最优
是否可以用其他方法?如记忆化搜索,但 DP 更直观易实现
是否可以优化空间?可以压缩部分维度,但对本题意义不大

十一、总结

项目内容
算法类型区间动态规划(Interval DP)
时间复杂度O(n³)
空间复杂度O(n²)
核心技巧区间划分 + 最小化三角形乘积和
注意事项使用 __int128 防止溢出
类似题目矩阵链乘法、石子合并、最长回文子串

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值