【例 3】凸多边形的划分(信息学奥赛一本通- P1571)

【题目描述】

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

【输入】

输入第一行为顶点数 N

第二行依次为顶点 1 至顶点 N 的权值。

【输出】

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

【输入样例】

复制代码
5
121 122 123 245 231

【输出样例】

复制代码
12214884

【提示】

数据范围与提示:

对于 100% 的数据,有 N≤50,每个点权值小于 109 。

在算法竞赛中,我们经常会遇到一类题目:数据规模N很小(例如),但涉及的数值运算却非常巨大,轻易就能挤爆long long

今天我们要看的这道经典例题------凸多边形的划分 ,就是这样一个典型的组合题:它既需要区间动态规划 的思维,又需要处理超高精度的数值运算。

题目简述

给定一个具有N个顶点的凸多边形(),每个顶点都有一个权值)。我们将这个多边形划分成N-2个互不相交的三角形。

划分的代价定义为:所有三角形的三个顶点的权值乘积之和。求最小的划分代价。

核心思路:区间动态规划

面对这种几何图形的分割问题,初学者往往容易陷入"几何切分"的死胡同。其实,我们应该将多边形的顶点看作一个线性的序列

我们的目标是解决整个区间[1, N]的问题。根据 DP 的思想,我们可以将其拆解为更小的子区间问题。

1. 状态定义

定义dp[i][j]表示:将顶点i到顶点j构成的子多边形(即顶点序列)划分成三角形所能得到的最小权值和。

  • 目标:求dp[1][N]。

  • 边界条件:当区间长度为 2 时(例如dp[i][i+1]),只有两个点,无法构成三角形,代价为 0。

2. 状态转移方程

如何计算大的区间dp[i][j]?

我们可以逆向思考:在最终的划分方案中,底边(i, j)一定属于某一个三角形。假设这个三角形的第三个顶点是k。

为了能构成三角形(i, k, j),顶点k必须严格位于i和j之间(即i<k<j)。

一旦选定了k,这个三角形就把问题划分成了三部分:

  1. 左子问题:顶点i到k的最小划分代价,即dp[i][k]。

  2. 右子问题:顶点k到j的最小划分代价,即dp[k][j]。

  3. 当前三角形代价

我们遍历所有可能的k,取最小值:

dp[i][j] =

隐藏的陷阱:数据溢出

DP 方程有了,看起来问题解决了。但且慢,看看数据范围:

每个权值

计算一个三角形的代价需要三个权值相乘:

  • int 最大值约为

  • long long 最大值约为

很显然, 远远超出了long long的承受范围。如果我们直接用 long long 计算,结果一定会溢出变成负数,导致答案错误。

物理外挂:__int128

在标准 C++ 中,要处理这么大的数,通常需要手写高精度结构体(BigInt)。但在竞赛环境中(使用 GCC 编译器),我们有一个"物理外挂"可以使用:__int128

什么是 __int128

它是一个 GCC 编译器扩展提供的 128 位整数类型,能存储高达的数值,足以应对本题的。它的加减乘除运算和普通整数一样方便。

唯一的缺点:I/O 问题

因为 __int128 不是 C++ 标准类型,标准的输入输出流(cin / cout)并不认识它。如果我们直接写 cout << dp[1][N],编译器会报错。

因此,我们需要手写一个简单的输出函数(快写)。利用递归思想,将大数字拆解成一个个字符输出。

cpp 复制代码
//手写输出函数
void print(int128 x){
    if(x>9) print(x/10);//递归打印前面的位
    putchar(x%10+'0');//打印最后一位
}

完整代码

结合区间 DP 的逻辑和 __int128 的高精度处理,我们可以写出最终的完整代码。

cpp 复制代码
//这道题数据范围大 要用高精度 第一种用__int128
#include <iostream>
#include <algorithm>//min函数
#include <cstdio>//putchar
using namespace std;
typedef __int128 int128;
int n;
int a[110];//存每个点权值
int128 dp[110][110];//dp[i][j]代表i-j顶点区间内的权值乘积和的最小值

//手写输出函数
void print(int128 x){
    if(x>9) print(x/10);//递归打印前面的位
    putchar(x%10+'0');//打印最后一位
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];//存每个点权值
    //将DP数组初始化为一个极大的值(无穷大)
    //利用位运算(int128)1 << 120 可以安全地构造一个超大数
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
          dp[i][j]=(int128)1<<120;
        }
    }
    for(int i=1;i<n;i++){//一个点或两个点所构成的区间无法构成三角形,因此权值为0
        dp[i][i]=0;
        dp[i][i+1]=0;
    }
    dp[n][n]=0; 
    //枚举区间长度len,从3开始,2个无法构成三角形
    for(int len=3;len<=n;len++){
        for(int i=1;i<=n-len+1;i++){//左端点
            int j=i+len-1;//右端点
        //枚举分割点k,k必须严格位于i和j之间 
        //k如果等于i或者j无法构成三角形
            for(int k=i+1;k<j;k++){
                dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+int128(a[i])*a[k]*a[j]);
            }
        }
    }
    print(dp[1][n]);
    return 0;
}

总结

这道题是区间DP分割类的经典例题。它教会了我们两件事:

  1. 思维转换:将几何图形的分割问题转化为线性区间的合并问题。

  2. 高精度意识 :在计算乘积时,一定要估算最大可能的数值范围,当long long不够用时,果断使用 __int128(在允许的环境下)或手写高精度。

相关推荐
斯内科7 分钟前
四胞胎素数:找出‌个位数分别是 1、3、7、9‌,且‌十位及更高位数字完全相同‌的质数,例如 11、13、17、19
算法·质数·素数·四胞胎素数
Hello.Reader38 分钟前
算法基础(十二)——主方法:快速求解常见递归式
算法
小O的算法实验室1 小时前
2026年IEEE TITS,面向按需外卖配送调度的特定问题知识与基于学习元启发式算法,深度解析+性能实测
算法·论文复现·智能算法·智能算法改进
加勒比海带661 小时前
目标检测算法——农林行业数据集汇总附下载链接【Plant】
大数据·图像处理·人工智能·算法·目标检测
洛水水1 小时前
【力扣100题】23. 螺旋矩阵
算法·leetcode·矩阵
影sir1 小时前
不同测试数据下,该如何选择算法
算法·深度优先
潇湘散客2 小时前
CAX软件插件化设计实现牛刀小试
c++·算法·图形学·opengl
速易达网络2 小时前
2026,视觉算法正在经历一场静默革命
算法
WBluuue2 小时前
Codeforces 1094 Div1+2(ABCDE)
c++·算法
TENSORTEC腾视科技2 小时前
腾视科技大模型一体机解决方案:低成本私有化落地,重塑行业智能应用新格局
大数据·人工智能·科技·算法·ai·零售·大模型一体机