题目描述:
给定一个具有 N 个顶点的凸多边形,将顶点从 1 至 N 标号,每个顶点的权值都是一个正整数。
将这个凸多边形划分成 N−2个互不相交的三角形,对于每个三角形,其三个顶点的权值相乘都可得到一个权值乘积,试求所有三角形的顶点权值乘积之和至少为多少。
输入格式
第一行包含整数 N,表示顶点数量。
第二行包含 N 个整数,依次为顶点 1 至顶点 N 的权值。
输出格式
输出仅一行,为所有三角形的顶点权值乘积之和的最小值。
数据范围
N≤50,
数据保证所有顶点的权值都小于10^9
输入样例:
5
121 122 123 245 231
输出样例:
12214884
分析:
首先需要考虑如何将本题划分为若干个相似的子问题。
如上图所示,一个编号为1到6的六边形,我们考虑划分时边16会被哪个三角形选中,顶点1,6构成的三角形可以是126,136,146,156。比如随便选取其中的一个三角形146,原图形就被划分为了三部分,最简单的蓝色三角形146,以及蓝色三角形上面的图形和下面的图形,由于划分的三角形不能交叉,所以不会存在像125这样会与146交叉的三角形,也就是说,这三部分,任意一部分都是一个独立的子问题。我们考虑了由顶点1和N构成的三角形后,剩下的两部分都是与原问题相同但是规模小于原问题的独立子问题,这就类似于石子合并问题,找到到中间蓝色三角形的代价,然后加上上下部分代价就是原问题的代价了。即f[i][j]表示顶点编号i到j多边形中所有三角形权值乘积之和的最小值,状态转移方程为f[i][j] = min(f[i][j],f[i][k]+f[k][j] + w[i]*w[j]*w[k])。本来是道简单的区间DP问题,但是数据范围较大,需要用高精度实现加法和乘法运算。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#define vi vector<int>
using namespace std;
typedef long long ll;
const int N = 55;
int a[N];
vi f[N][N];
void add(vi& x,vi& y,vi& z){
if(x.size() < y.size()) add(y,x,z);
else{
int t = 0;
for(int i = 0;i < x.size();i++){
t += x[i];
if(i < y.size()) t += y[i];
z.push_back(t % 10);
t /= 10;
}
if(t) z.push_back(t);
}
}
void mul(vi& x,ll y,vi& z){
ll t = 0;
for(int i = 0;i < x.size() || t;i++){
if(i < x.size()) t += x[i] * y;
z.push_back(t % 10);
t /= 10;
}
}
bool cmp(vi& x,vi& y){
if(x.size() != y.size()) return x.size() < y.size();
int i = x.size() - 1;
while(i && x[i] == y[i]) i--;
return x[i] < y[i];
}
int main(){
int n;
cin>>n;
for(int i = 1;i <= n;i++) cin>>a[i];
for(int len = 3;len <= n;len++){
for(int l = 1;l+len-1<= n;l++){
int r = l + len -1;
for(int k = l + 1;k < r;k++){
vi res,s,t(1,a[l]);
mul(t,a[r],s),mul(s,a[k],res);
s.clear(),t.clear();
add(f[l][k],f[k][r],s),add(s,res,t);
if(f[l][r].empty() || cmp(t,f[l][r])) f[l][r] = t;
}
}
}
for(int i = f[1][n].size() - 1;i >= 0;i--) cout<<f[1][n][i];
return 0;
}