【例 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. 状态定义

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

  • 目标:求dp1N

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

2. 状态转移方程

如何计算大的区间dpij

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

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

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

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

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

  3. 当前三角形代价

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

dpij =

隐藏的陷阱:数据溢出

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(在允许的环境下)或手写高精度。

相关推荐
阿文的代码库2 分钟前
如何解决缺少特定算法思维的问题?
算法
yuan199972 分钟前
基于人工神经网络(ANN)的独立成分分析(ICA)算法
算法
代码地平线3 分钟前
C++ 入门篇类和对象·上篇:从本质深剖类与对象与C++基本用法
c语言·开发语言·数据结构·c++·笔记·算法
Hali_Botebie4 分钟前
期望最大化算法,Expectation-Maximization Algorithm
算法
weixin_4684668514 分钟前
通义千问核心能力与实战表现深度评测
人工智能·深度学习·算法·ai·大模型
菜菜的顾清寒17 分钟前
力扣HOT100(48)图论-腐烂的橘子
算法·leetcode·图论
Ulyanov26 分钟前
深入QML滑块与进度控制:构建动态数据可视化界面:QML+PySide6现代开发入门(六)
开发语言·python·算法·ui·信息可视化·雷达电子对抗仿真
星马梦缘26 分钟前
ACM笔记 学习版本
数据结构·c++·算法
CQU_JIAKE28 分钟前
6.1【A】
算法
wayz1131 分钟前
Momentum:CTI(相关趋势指标)技术指标详解
算法·金融·数据分析·量化交易·特征工程