[ACM_SMU_1104]最优矩阵连乘积(区间dp)

来源:互联网 发布:蜘蛛纸牌算法 编辑:程序博客网 时间:2024/06/05 07:08

题目链接:http://218.5.241.13:8060/oj/showProblem.jsp?pid=1104

转载自:蓝飞技术部落格

题目大意:求那个矩阵连乘的最小运算量

思路是采用动态规划:


设a[n][m]为第n个矩阵到第m个矩阵连乘的最小乘法数(n, m >= 1),b[i], b[i -1]为第i个矩阵的行数和列数(i >= 1),那么:

1.a[n][n + 1]易求,为相邻两个矩阵相乘的乘法数,即b[n - 1] * b[n] * b[n + 1];
2.  An - Am可以任意拆分为An - Ai及Ai+1 - Am两部分相乘(n <= i <= m),则a[n][m]为所有拆分情况中乘法次数最少的一种,即a[n][m] = min(a[n][i] + a[i+1] [m]+ b[n - 1] * b[i] * b[m]),注意b的下标是从0开始,比a的下标少1。

这样说可能比较抽象,我们举个例子:

 A1 * A2 * A3 * A4可以拆分为以下几种形式,且最小乘法数必是以下情况中的最乘法数量:
 A1 * (A2 * A3 * A4)
 (A1 * A2) * (A3 * A4)
 (A1 * A2 * A3) * A4

 而A1 * A2 * A3与A2 * A3 * A4又可以拆分为以下两种形式:
A1 * (A2 * A3)

(A1 * A2) * A3
以此类推,一直到相邻两个矩阵的相乘,而两个矩阵的最小乘法数即是两个矩阵相乘所需的乘法数,易求。

而计算顺序则与分析相反,由两两相邻的矩阵开始,一直推算到所有矩阵的最优结果。以第二个测试数据为例,a[n][m]计算结果如下:


易知计算顺序为由左下往右上,最终得出的a[1][6](表示从第1个矩阵到第6个矩阵的最小乘法数量)便是所求答案。

一种思路是三层循环嵌套,第一层为所求矩阵个数,第二层为所求矩阵开始位置,第三层为拆分的所有子情况,代码如下:

#include<iostream>#include<vector>using namespace std;inline int min(int a, int b){    return a > b ? b : a;}int main(){    int t, i, j, r, a[101][101], b[101];    while(cin >> t && t){        for(i = 1; i <=t; ++i){            cin >> b[i-1] >> b[i];        }        for(i = 0; i <= 100; ++i){            for(j = 0; j <= 100; ++j){                a[i][j] = 0;    //全部归零            }        }        for(r = 2; r <= t; ++r){    //求相邻r个矩阵的最优值            for(i = 1; i + r - 1 <= t; ++i){    //从第i个矩阵开始的r个矩阵                int num = 0xffffff;    //定义一个较大的值                for(j = i; j < i + r - 1; ++j){                //从第i个矩阵开始的r个矩阵拆分为i到j和j+1到i+r-1两个部分                    num = min(num,                a[i][j] + a[j + 1][i + r - 1] + b[i - 1] * b[j] * b[i + r - 1]);                }                a[i][i + r - 1] = num;            }        }        cout << a[1][t] << endl;    }    return 0;}   
三层循环嵌套可能比较难理解,当时在实验室也是因为三层循环三个变量以及两个数组下标之间的关系没有理清楚所以搞的一片混乱,像这种关联数据非常多的情况应该先把关系分析好再来写代码,否则改来改去很难找清楚到底是哪里出的问题,后来直接把三个循环全删了重新写才把这道题做出来。

其实对于这种有点类似斐波那契数列的递推问题我们也可以通过递归来解决,从而免去两层循环:

#include<iostream>#include<vector>using namespace std;int b[101]; inline int min(int a, int b){return a > b ? b : a;}int GetMuls(int n, int m){if(n == m)return 0;int num = 0xffffff;//足够大的数for(int i = n; i < m; ++i){//将n - m的矩阵拆分为n到i及i + 1到m两个部分num = min(num, GetMuls(n, i) + GetMuls(i + 1, m) + b[n - 1] * b[i] * b[m]);//将最小值保存下来}return num;} int main(){int t, i;while(cin >> t && t){for(i = 1; i <= t; ++i){cin >> b[i-1] >> b[i];}cout << GetMuls(1, t) << endl;}return 0;}

使用递归代码顿时简洁了许多,甚至连a[n][m]都省了。虽然可以OJ上通过,但这种做法并不推荐,因为这是一种效率非常低的做法,类似斐波那契数列,该问题可以分解为两个子问题,而每个子问题又都可以分解为更小的两个子问题,递归的次数呈几何倍数增长,若测试数据较大的话这种算法必然超时。同样,我们也可以利用斐波那契数列问题的解决思想,将计算过的值先储存起来,再次用到的时候直接返回,用空间换时间。剪枝后的代码如下:

推荐方法!!

#include<iostream>#include<vector>using namespace std;int a[101][101], b[101], sum = 0; inline int min(int a, int b){return a > b ? b : a;} int GetMuls(int n, int m){if(n == m)return 0;if(a[n][m] > 0)//如果a[n][m]已经求出return a[n][m];//直接返回a[n][m]的值++sum;int num = 0xffffff;for(int i = n; i <m; ++i){num = min(num, GetMuls(n, i) + GetMuls(i + 1, m) + b[n - 1] * b[i] * b[m]);}a[n][m] = num;//保存进数组中return num;} int main(){int t, i, j;while(cin >> t && t){for(i = 1; i <= t; ++i){cin >> b[i-1] >> b[i];}for(i = 0; i <= 100; ++i){for(j = 0; j <= 100; ++j){a[i][j] = 0;}}cout << GetMuls(1, t) << endl;//cout << sum << endl;}return 0;}



原创粉丝点击