Problem 1104 最优矩阵连乘积
Accepted: 29 Total Submit: 44Time Limit: 1000ms Memony Limit: 32768KB
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的一个计算次序(完全加括号方式),使得依此次序计算矩阵连乘积需要的数乘次数最少。
Input
有若干种案例,每种两行,第一行是一个非负整数n表示矩阵的个数,n=0表示结束。接着有n行,每行两个正整数,表示矩阵的维数。
Ouput
对应输出最小的乘法次数。
Sample Input
310 100100 55 500
Sample Output7500
ABCD四个矩阵连乘
1、(A(BCD))——>(A(B(CD))),(A((BC)D));
2、((AB)(CD))——>NULL;
3、((ABC)D)——>((A(BC)D)),(((AB)C)D);
对于上面四个矩阵来说,枚举方法是:
1、括号加在A和B之间,矩阵链被分为(A)和(BCD);
2、括号加在B和C之间,矩阵链被分为(AB)和(CD);
3、括号加在C和D之间,矩阵链被分为(ABC)和(D);
在第一步中分出的(A)已经不能在加括号了,所以结束;
而(BCD)继续按照上面的步奏把括号依次加在B和C、C和D之间,其他情况相同。
加括号的过程是递归的。
备忘录法优化
上图为递归枚举过程,小方块内的1:4代表第1个矩阵至第4个矩阵的完全加括号方式
可以看到黄色方块中有很多重复计算,所以利用备忘录来保存计算结果,在每次进行计算前,
先查表,看是否计算过,避免重复计算。
#include<iostream>
#include<string.h>
using namespace std;
#define N 50
#define INF 9999999999
long long m[N][N];
int s[N][N],p[N];
int main()
{
int n;
while(cin>>n&&n)
{
int i,j,k;
int x[N];
for(i=0;i<2*n;++i)
cin>>x[i];
p[0]=x[0];
//p[1]=x[1];
j=1;
for(i=1;i<2*n;i=i+2)
p[j++]=x[i];
memset(m,0,sizeof(m));
memset(s,0,sizeof(s));
int l;
for(l=2;l<=n;++l)
{
for(i=1;i<=n-l+1;++i)
{
j=i+l-1;
m[i][j]=INF;
for(k=i;k<=j-1;++k)
{
long long q;
q=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
if(q<m[i][j])
{
m[i][j]=q;
s[i][j]=k;
}
}
}
}
cout<<m[1][n]<<endl;
}
return 0;
}
递归方法:
#include<iostream>
#include<string.h>
using namespace std;
#define N 50
#define INF 99999999
int m[N][N],s[N][N];
int p[N];
int n;
int matrix(int m[N][N],int p[N],int i,int j)
{
if(i==j)
m[i][j]=0;
if(m[i][j]<INF)
return m[i][j];
else
{
for(int k=i;k<j;++k)
{
int q=matrix(m,p,i,k)+matrix(m,p,k+1,j)+p[i-1]*p[k]*p[j];
if(q<m[i][j])
{
m[i][j]=q;
s[i][j]=k;
}
}
return m[i][j];
}
}
int main()
{
while(cin>>n&&n)
{
int i,j;
int x[N];
for(i=0;i<2*n;++i)
cin>>x[i];
p[0]=x[0];
j=1;
for(i=1;i<2*n;i=i+2)
p[j++]=x[i];
//for(i=0;i<=n;++i)
// cin>>p[i];
memset(m,125,sizeof(m));
memset(s,0,sizeof( s));
matrix(m,p,1,n);
cout<<m[1][n]<<endl;
}
}
上面是备忘录法,下面是无备忘录法:
#include<iostream>
#include<string.h>
using namespace std;
#define N 50
#define INF 99999999
int m[N][N],s[N][N];
int p[N];
int n;
int matrix(int p[N],int i,int j)
{
if(i==j)
return 0;
m[i][j]=INF;
//else
{
for(int k=i;k<j;++k)
{
int q=matrix(p,i,k)+matrix(p,k+1,j)+p[i-1]*p[k]*p[j];
if(q<m[i][j])
{
m[i][j]=q;
s[i][j]=k;
}
}
return m[i][j];
}
}
int main()
{
while(cin>>n&&n)
{
int i,j;
int x[N];
for(i=0;i<2*n;++i)
cin>>x[i];
p[0]=x[0];
j=1;
for(i=1;i<2*n;i=i+2)
p[j++]=x[i];
//for(i=0;i<=n;++i)
// cin>>p[i];
memset(m,0,sizeof(m));
memset(s,0,sizeof( s));
matrix(p,1,n);
cout<<m[1][n]<<endl;
}
}
动态规划法
以矩阵链ABCD为例
按照矩阵链长度递增计算最优值
矩阵链长度为1时,分别计算出矩阵链A、B、C、D的最优值
矩阵链长度为2时,分别计算出矩阵链AB、BC、CD的最优值
矩阵链长度为3时,分别计算出矩阵链ABC、BCD的最优值
矩阵链长度为4时,计算出矩阵链ABCD的最优值
动归方程:
k为矩阵链断开的位置
d数组存放矩阵链计算的最优值,d[i][j]是以第i个矩阵为首,第j个矩阵为尾的矩阵链的最优值,i > 0
m数组内存放矩阵链的行列信息,m[i-1]和m[i]分别为第i个矩阵的行和列(i = 1、2、3...)
下面是加括号输出:
#include<iostream>
#include<string.h>
using namespace std;
#define N 50
#define INF 99999999
int m[N][N],s[N][N];
void print(int s[N][N],int i,int j)
{
if(i==j)
cout<<"A"<<i;
else
{
cout<<"(";
print(s,i,s[i][j]);
print(s,s[i][j]+1,j);
cout<<")";
}
}
int main()
{
int n;
while(cin>>n&&n)
{
memset(m,0,sizeof(m));
memset(s,0,sizeof(s));
int p[N];
int i,j,k,l;
for(i=0;i<=n;++i)
cin>>p[i];
for(l=2;l<=n;++l)
{
for(i=1;i<=n-l+1;++i)
{
j=i+l-1;
m[i][j]=INF;
for(k=i;k<=j-1;++k)
{
int q=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
if(q<m[i][j])
{
m[i][j]=q;
s[i][j]=k;
}
}
}
}
print(s,1,n);
cout<<endl;
cout<<m[1][n]<<endl;
}
}