【例 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 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
偷吃的耗子8 小时前
【CNN算法理解】:三、AlexNet 训练模块(附代码)
深度学习·算法·cnn
2013编程爱好者8 小时前
【C++】树的基础
数据结构·二叉树··二叉树的遍历
NEXT068 小时前
二叉搜索树(BST)
前端·数据结构·面试
化学在逃硬闯CS9 小时前
Leetcode1382. 将二叉搜索树变平衡
数据结构·算法
ceclar1239 小时前
C++使用format
开发语言·c++·算法
Gofarlic_OMS9 小时前
科学计算领域MATLAB许可证管理工具对比推荐
运维·开发语言·算法·matlab·自动化
夏鹏今天学习了吗9 小时前
【LeetCode热题100(100/100)】数据流的中位数
算法·leetcode·职场和发展
忙什么果10 小时前
上位机、下位机、FPGA、算法放在哪层合适?
算法·fpga开发
董董灿是个攻城狮10 小时前
AI 视觉连载4:YUV 的图像表示
算法