区间动态规划是线性动规的拓展,在划分阶段时,往往是以区间的长度从小到大为阶段,逐步求解到到长度为N的区间的最优值,在枚举每一个区间的最优值时,由于当前区间内又有很多种合并方式并到到当前区间,那么就需要枚举这些合并方式中产生的值维护最优值,合并的不同,可以看作是区间划分的不同,划分时需要枚举划分的位置,即分割点。
那么对于区间类动态规划问题,往往可以将问题分解成为两两合并的形式。其解决方法是对整个问题设最优解,枚举分割点,维护最优值。
dp[i][j] = max{dp[i][k] + dp[k+1][j] + 合并时需要计算的值 },其中k为区间[i,j]内一分割点。
回到问题凸多边形的划分这道题。
题目描述
给定一个具有N(N<=50)个顶点(从1到N编号)的凸多边形,每个顶点的权均是一个正整数。问:如何把这个凸多边形划分成N-2个互不相交的三角形,使得这些三角形顶点的权的乘积之和最小?
输入
输入文件的第一行为顶点数N;第二行为N个顶点(从1到N)的权值。
输出
只有一行为这些三角形顶点的权的成绩之和的最小值。
样例输入
5
121 122 123 245 231
样例输出
12214884
分析:
对于多边形,我们将其顶点按顺时针编号,那么所求问题为1~n顺时针连线组成的多边形的三角形划分后的权值乘积之和的最小值。按照求什么设什么的思考原则,设dp[i][j](i < j)表示从i~j顺时针连线组成的多边形的三角形剖分后所得的顶点权值乘积和的最小值。
我们可以找到一个k,i < k < j,使其将ij所组成的多边形分成3份,剖出来的三份分别是ik构成的多边形,ijk三个点构成的三角形和k~j构成的多边形,如下图所示。
那么dp[i][j] = min{ dp[i][k] + a[i] * a[j] * a[k] + dp[k][j] }(1<=i < k < j < = n);
初始dp[i][i+1] = 0;
目标状态在dp[1][n];
这道题涉及到多个三个数的乘积之和,计算结果会非常大,需要使用高精度。
下面给出没有用高精度的50分程序供看官纯粹地去理解动规过程。
#include<bits/stdc++.h>
using namespace std;
long long dp[51][51],a[51];
int main(){
int n;
memset(dp,0x7f,sizeof(dp));
cin >> n;
for(int i=1;i<= n;i++) cin >> a[i];
for(int i= 1;i<= n;i++){
dp[i][i+1] = 0;
}
for(int t= 2;t <= n-1;t++){
for(int i = 1;i<= n-t; i++){
int j = i + t;
for(int k = i+1;k<= j-1;k ++){
dp[i][j] = min(dp[i][j],dp[i][k]+dp[k][j]+1LL*a[i]*a[j]*a[k]);
}
}
}
cout << dp[1][n];
return 0;
}
在最后再上AC程序,写得不好勿喷。
#include<bits/stdc++.h>
using namespace std;
long long a[51];
const int MAXN=202;
struct Bignum{
int len,s[MAXN*2];
Bignum(){
memset(s,0,sizeof(s));
len = 1;
}
Bignum operator +(const Bignum &A)const{
Bignum B;
B.len = max(len,A.len);
for(int i = 0;i< B.len; i++){
B.s[i] =B.s[i] + A.s[i] + s[i];
B.s[i+1] += B.s[i]/10;
B.s[i] = B.s[i]%10;
}
if(B.s[B.len]) B.len ++;
return B;
}
bool operator <(const Bignum &A)const{
if(len != A.len) return len < A.len;
for(int i= len-1; i >=0 ; i--){
if(s[i] != A.s[i]) return s[i] < A.s[i];
}
return false;
}
Bignum operator *(const Bignum &A)const{
Bignum B;
B.len = len+A.len-1;
for(int i = 0;i< A.len; i++){
for(int j=0;j< len;j++){
B.s[i+j] =B.s[i+j] + A.s[i] * s[j];
B.s[i+j+1] += B.s[i+j]/10;
B.s[i+j] = B.s[i+j]%10;
}
}
if(B.s[B.len]) B.len ++;
return B;
}
void write(){
int k = len-1;
while(s[k]==0 && k >0) k--;
for(int i = k;i>=0;i--) cout << s[i];
}
}dp[51][51];
Bignum change(long long x){ //将一个整数变成数组存储的Bignum类型
Bignum t;
int cnt =0;
while(x!=0){
t.s[cnt++] = x%10;
x /= 10;
}
t.len = cnt;
return t;
}
int main(){
int n;
cin >> n;
for(int i=1;i<= n;i++) cin >> a[i];
for(int t= 2;t <= n-1;t++){ //阶段:枚举连续的t+1个点组成的t+1边形
for(int i = 1;i<= n-t; i++){ //状态:枚举当前阶段的连续区间的起点
int j = i + t; //计算当前区间的终点
dp[i][j].len = 400; //将dp[i][j]初始化为一个大数
dp[i][j].s[399] = 1;
//枚举k, 其中i<k<j , i,j,k构成的三角形将i~j 号点组成的多边形分成三份
// 这三份分别是i~k组成的三角形,ijk三个点组成的一个三角形和k~j组成的多边形。
for(int k = i+1;k<= j-1;k ++){
//注意a[i]*a[j]*a[k]会比较大,此处也需要高精度,
Bignum t = change(a[i]) * change(a[j]) * change(a[k]);
Bignum tp = dp[i][k] + dp[k][j] + t;
if(tp < dp[i][j]) dp[i][j] = tp;
}
}
}
dp[1][n].write();
return 0;
}