矩阵最优连乘问题(区间DP+记忆化)
Description
在科学计算中经常要计算矩阵的乘积。矩阵A和B可乘的条件是矩阵A的列数等于矩阵B的行数。若A是一个p×q的矩阵,B是一个q×r的矩阵,则其乘积C=AB是一个p×r的矩阵。
由公式知计算C=AB总共需要pqr次的数乘。
为了说明在计算矩阵连乘积时加括号方式对整个计算量的影响,我们来看一个计算3个矩阵{A1,A2,A3}的连乘积的例子。设这3个矩阵的维数分别为10×100,100×5和5×50。若按第一种加括号方式((A1A2)A3)来计算,总共需要10×100×5+10×5×50=7500次的数乘。若按第二种加括号方式(A1(A2A3))来计算,则需要的数乘次数为100×5×50+10×100×50=75000。第二种加括号方式的计算量是第一种加括号方式的计算量的10倍。由此可见,在计算矩阵连乘积时,加括号方式,即计算次序对计算量有很大影响。
于是,人们自然会提出矩阵连乘积的最优计算次序问题,即对于给定的相继n个矩阵{A1,A2,…,An}(其中Ai的维数为pi-1×pi ,i=1,2,…,n),如何确定计算矩阵连乘积A1A2…An的一个计算次序(完全加括号方式),使得依此次序计算矩阵连乘积需要的数乘次数最少。
输入数据第一行为矩阵个数,第二行为n+1个数字a[0~n],其中对于第i个矩阵其大小为a[i-1]*a[i]。
刚刚学习了区间DP,写发题解纪念一下。
按照区间DP的思想,可以倒着分析,枚举最后一次进行乘法的位置,将原区间转化为两个子区间,相应代表了将原问题转化为两个规模较小的子问题。这里可以将递归和记忆化结合起来,递归的终点就是区间左端点等于右端点,由于提前给dp[i][i]赋值为0,所以这里直接return 0;同时如果dp[l][r]不为初始化时的0x3f3f3f3f的话说明这个值已经得到了就不用再算一遍了,直接return就好(记忆化)。然后就是dp[l][r]还没被计算过,那么枚举每个可能为当前区间最后一次做乘法的位置:l~r-1,然后写转移方程dp[l][r]=min(dp[l][r],process(l,i)+process(i+1,r)+a[l-1]*a[i]*a[r])(注意这里的dp[l][r]不要写成process(l,r)要不然永远无法更新,直接死循环了),至于为啥加的是a[l-1]*a[i]*a[r],这是根据矩阵乘法的性质得来的,分开的左半个矩阵的大小为a[l-1]*a[i],右半个矩阵的大小为a[i]*a[r],算出来后直接return即可。最终要求的就是process(1,n)。
#include <bits/stdc++.h> using namespace std; int n,a[1005];//第i个矩阵是a[i-1]*a[i]大小 int dp[1005][1005]={0}; int process(int l,int r) { if(dp[l][r]!=0x3f3f3f3f)return dp[l][r]; int i; for(i=l;i<r;i++) { dp[l][r]=min(dp[l][r],process(l,i)+process(i+1,r)+a[l-1]*a[i]*a[r]);//这里的dp[l][r]不要写成process(l,r)要不然永远无法更新,直接死循环了 } return dp[l][r]; } int main() { cin>>n; int i; for(i=0;i<=n;i++)scanf("%d",&a[i]); memset(dp,0x3f3f3f3f,sizeof(dp)); for(i=1;i<=n;i++)dp[i][i]=0; printf("%d",process(1,n)); return 0; }