实验内容:
算法讲解:
1.算法模型引入(区间动态规划)
首先,这个问题是一个很经典的区间动态规划问题,我们先看一下什么是区间动态规划:
顾名思义,就是动态规划过程中求一个区间的最优解。通过将一个大的区间分为很多个小的区间,求其小区间的解,然后一个一个的组合成一个大的区间而得出最终解,有没有发现,这完全就是分治的思想。
不过这样讲解还是很抽象,那么我们举一个很简单的具体的例子吧:
有
2.具体问题分析
(1)分析模型:
首先为了简化问题,我们把问题进行一个小的转化假设:假设有 n个数和 n−1个运算符( +或 ∗ ),现在我们要输出一种组合方式使得这个式子的值尽量大,那么我们能改变的是什么呢?我们能改变的就是这些运算的顺序!
例如:
已知序列: 3∗2+1 ,那么我们就有两种选择方法:选择一: (3∗2)+1 。选择二: 3∗(2+1)。
那么我们就可以把这个过程看做是两个区间的合并问题,现在有 3个区间: [1,1],[2,2],[3,3],那么我们要把它合并成一个区间,就有两种合并方法:
方法一:$[1,1],[2,2] ,[3,3] --> [1,2] , [3,3] --> [1,3] $
方法二:$[1,1],[2,2] ,[3,3] --> [1,1] , [2,3] --> [1,3] $
那么我们自然的希望可以从最终的情况[1,3]枚举出每一种组合出它的情况,显然区间[1,3]一定是由两个子区间组成的,那么我们只需要枚举断点,就可以枚举出每一种组成它的情况,对于每一种组成它的情况所需要的两个子区间:区间L,区间R,我们就递归的去枚举子区间的组合方法,从而达到枚举每一种情况的目的。
(2)动态规划经典三步:
第一步:定义状态
dpmax(i,j)表示区间 [i,j]能组成的最大值。
dpmin(i,j)表示区间 [i,j]能组成的最小值。
第二步:确定状态转移方程
dpmax(l,r)=⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧max{dpmax(l,i)+dpmax(i+1,r) ∣ l≤i<r},op[i]==′+′max⎣⎢⎢⎡dpmax(l,i)∗dpmax(i+1,r)dpmin(l,i)∗dpmax(i+1,r)dpmax(l,i)∗dpmin(i+1,r)dpmin(l,i)∗dpmin(i+1,r)⎦⎥⎥⎤l≤i<r,op[i]==′∗′
dpmin(l,r)=⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧min{dpmin(l,i)+dpmin(i+1,r) ∣ l≤i<r},op[i]==′+′min⎣⎢⎢⎡dpmax(l,i)∗dpmax(i+1,r)dpmin(l,i)∗dpmax(i+1,r)dpmax(l,i)∗dpmin(i+1,r)dpmin(l,i)∗dpmin(i+1,r)⎦⎥⎥⎤l≤i<r,op[i]==′∗′
第三步:确定初始状态(边界状态)
dpmax(x,x)=dpmin(x,x)=val[x]
注: val[x]为第x个数字的值, op[x] 为第 x个运算符的类型( +为 0, ×为 1), n为区间总长度,该序列共有 n个数字和 n−1个运算符。
(3)总体结构简述
通过上面的分析,我们就得到了一个求得任意计算序列最大值的方法。那么我们只需要从最大区间开始向下递归计算每一个小区间的最大值,就可以解决问题了。现在只剩下最后一个问题就是如何处理环结构,这个也很简单,只需要把原来的序列再后面复制一遍得到一个长度为 2∗n 的序列,然后计算出这个序列中每一个长度为 n 的子序列的 dpmax 的值,然后返回最大的值即为答案。
3.时间复杂度分析
时间复杂度为:状态数 O(n2) ∗状态转移枚举次数 O(n)=O(n3)
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 105 ;
struct CalSeq{ //存储一个计算序列的信息
string str;
long long int val;
void print() {
printf( "%s = %lld \n", str.c_str(), val ) ;
}
};
bool operator < ( const CalSeq &l , const CalSeq &r ) { // 重载小于运算符
return l.val < r.val ;
}
int n, val[maxn] ;
char op[maxn] ;
CalSeq dpmax[maxn][maxn], dpmin[maxn][maxn] ;
string getValToString ( long long int x ) { // 数字 x 转化成string类型
string ret ;
if ( x == 0 ) {
ret += '0' ;
}
if ( x < 0 ) {
ret += '-' ;
x= -x ;
}
while ( x>0 ) {
ret += (x%10)+'0';
x/=10;
}
return ret;
}
void Input(){ // 输入序列
scanf( "%d", &n ) ;
for( int i=0; i<n; i++ ) {
scanf( "%d", &val[i] ) ;
while( scanf( "%c", &op[i] ) != EOF ) {
if ( op[i] == '+' || op[i] == '*' ) break ;
}
}
}
void copySeq(){ // 复制序列
for( int i=0; i<n; i++ ) {
op[i+n] = op[i] ;
val[i+n] = val[i] ;
}
}
void Init(){ // 初始化长度为1的计算序列的信息
for( int i=0; i<2*n; i++ ) {
dpmax[i][i].str = dpmin[i][i].str = getValToString(val[i]);
dpmax[i][i].val = dpmin[i][i].val = val[i] ;
}
}
long long int calDpmax( int l , int r ) ; // 计算 val[l] 到 val[r] 的所有元素以某种顺序结合的能得到的最大值
long long int calDpmin( int l , int r ) ; // 计算 val[l] 到 val[r] 的所有元素以某种顺序结合的能得到的最小值
int main()
{
Input() ;
copySeq() ;
Init() ;
calDpmax( 0 , n-1 ) ;
CalSeq ans = dpmax[0][n-1] ;
for( int i=1; i<n; i++ ) { // 枚举每一个长度为 n 的连续序列,找到最大值
calDpmax( i , i+n-1 ) ;
ans = max( ans, dpmax[i][i+n-1] ) ;
}
ans.print();
return 0;
}
long long int calDpmax( int l , int r ) {
if ( r-l+1 > n ) return -1 ; // 长度超过n的没必要计算
if ( dpmax[l][r].str.length() > 0 ) { // 以前计算过的可以直接记忆化
return dpmax[l][r].val ;
}
int best_k = -1 ;
long long int best_val ;
string seql , seqr ;
for( int k=l; k<r; k++ ) { // 枚举分割点k,即l到r最后的连接点
if ( op[k] == '+' ) { // '+'
long long int temp = calDpmax(l,k) + calDpmax(k+1,r) ;
if ( best_k==-1 || best_val < temp ) {
best_k = k ;
best_val = temp ;
}
}
else { // '*'
long long int temp = calDpmax(l,k)*calDpmax(k+1,r); // max*max
seql = dpmax[l][k].str;
seqr = dpmax[k+1][r].str;
if ( temp < calDpmin(l,k)*calDpmin(k+1,r) ) { // min*min
temp = dpmin[l][k].val * dpmin[k+1][r].val ;
seql = dpmin[l][k].str;
seqr = dpmin[k+1][r].str;
}
if ( temp < calDpmin(l,k)*calDpmax(k+1,r) ) { // min*max
temp = dpmin[l][k].val * dpmax[k+1][r].val ;
seql = dpmin[l][k].str;
seqr = dpmax[k+1][r].str;
}
if ( temp < calDpmax(l,k)*calDpmin(k+1,r) ) { // max*min
temp = dpmax[l][k].val * dpmin[k+1][r].val ;
seql = dpmax[l][k].str;
seqr = dpmin[k+1][r].str;
}
if ( best_k==-1 || best_val < temp ) {
best_k = k ;
best_val = temp ;
}
}
}
if ( op[best_k] == '+' ) {
dpmax[l][r].val = best_val ;
dpmax[l][r].str = '(' + dpmax[l][best_k].str + '+' + dpmax[best_k+1][r].str + ')' ;
}
else {
dpmax[l][r].val = best_val ;
dpmax[l][r].str = seql + '*' + seqr ;
}
return best_val;
}
long long int calDpmin( int l , int r ) {
if ( r-l+1 > n ) return -1 ; // 长度超过n的没必要计算
if ( dpmin[l][r].str.length() != 0 ) { // 以前计算过的可以直接记忆化
return dpmin[l][r].val ;
}
int best_k = -1 ;
long long int best_val ;
string seql , seqr ;
for( int k=l; k<r; k++ ) { // 枚举分割点k,即l到r最后的连接点
if ( op[k] == '+' ) { // '+'
long long int temp = calDpmin(l,k) + calDpmin(k+1,r) ;
if ( best_k==-1 || best_val > temp ) {
best_k = k ;
best_val = temp ;
}
}
else { // '*'
long long int temp = calDpmax(l,k)*calDpmax(k+1,r); // max*max
seql = dpmax[l][k].str;
seqr = dpmax[k+1][r].str;
if ( temp > calDpmin(l,k)*calDpmin(k+1,r) ) { // min*min
temp = dpmin[l][k].val * dpmin[k+1][r].val ;
seql = dpmin[l][k].str;
seqr = dpmin[k+1][r].str;
}
if ( temp > calDpmin(l,k)*calDpmax(k+1,r) ) { // min*max
temp = dpmin[l][k].val * dpmax[k+1][r].val ;
seql = dpmin[l][k].str;
seqr = dpmax[k+1][r].str;
}
if ( temp > calDpmax(l,k)*calDpmin(k+1,r) ) { // max*min
temp = dpmax[l][k].val * dpmin[k+1][r].val ;
seql = dpmax[l][k].str;
seqr = dpmin[k+1][r].str;
}
if ( best_k==-1 || best_val > temp ) {
best_k = k ;
best_val = temp ;
}
}
}
if ( op[best_k] == '+' ) {
dpmax[l][r].val = best_val ;
dpmax[l][r].str = '(' + dpmax[l][best_k].str + op[best_k] + dpmax[best_k+1][r].str + ')' ;
}
else {
dpmax[l][r].val = best_val ;
dpmax[l][r].str = seql + op[best_k] + seqr ;
}
return best_val;
}
/* 5 3 * -2 + 1 * -4 + 5 * */