对整数序列(a0~an-1)求和,求和规则:不断取ai(0<i<n),和加上ai-1*ai*ai+1,去掉ai,重复以上步骤直到只剩两个数。求最小和。
思路:
类似矩阵链相乘,设dp[i][j]为序列(i,j)之间最小的sum。对序列(i,j),ai和aj最后会留下,所以最后一次求和一定是ai*ak*aj,(i<k<j)。以k为划分,则状态转移方程为dp[i,j] = min ( dp[i,j] , dp[i,k] + dp[k,j] + ai*ak*aj)
依赖关系分析:
求dp[i,j]要先求[i,k][k,j],即大区间依赖于小区间,因此从区间长度最小的开始遍历。要遍历全部小区间,遍历起点。所以有
for len = min_len to n
for start = 0 to n-1
/*状态转移,遍历k */
初始状态:
dp设为无穷大,区间长度最小为3,可以先将dp[i,i+2]算好,递推时len从4开始枚举。这里dp[i,i+1]要设为0,做题时WA卡在这里,因为更新状态时k从i+1开始枚举,而j=i+len-1,则会出现dp[i,j] = dp[i,i+2]=dp[i,i+1] + dp[k,j] + ai*ak*aj,为了保证计算正确应该赋值为0。
算法步骤:
初始化,然后递推求dp[i][j][k]。
算法复杂度
时间复杂度O(n3),空间复杂度O(n3)
代码:
1、递推
#include <string> #include <vector> #include <cstdio> #include <cstring> using namespace std;; const int inf = 0x3f; int n,a[110]; int dp[110][110]; int main() { cin >> n; for(int i=0;i<n;++i) scanf("%d",&a[i]); // dp 初始化 memset(dp,inf,sizeof(dp)); for (int i=0;i<n-1;++i) dp[i][i + 1] = 0; for (int i=0;i<n-2;++i) dp[i][i+2] = a[i]*a[i+1]*a[i+2]; for(int len=4;len<=n;++len){ for(int i=0;i<n;++i){ int j=i+len-1; if(j>n) break; // find a minimal state for dp[i][j] for(int k=i+1;k<j;++k) dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+a[i]*a[k]*a[j]); } } cout << dp[0][n-1] << endl; return 0; }
2、记忆化搜索
主函数执行f(0,n-1)就是答案,记忆化搜索代码如下:
int f (int a, int b){ if(dp[a][b]!=-1) return dp[a][b]; // 已经计算过了 if(b-a==1) return dp[a][b] = 0; // dp[i,i+1] int min = MAXN; for(int i = a+1;i <= b-1;i++){ int l = f(a,i), r = f(i,b); if (min > l + r + x[a]*x[i]*x[b]) min = l + r + x[a]*x[i]*x[b]; } return dp[a][b] = min; }