矩阵【DP】【记搜】

题目大意:
N N 个矩阵相乘,求进行乘法的最少次数,我们认为两个矩阵A(m×n)×B(n×p)的乘法次数为 m×n×p m × n × p 次。

Input I n p u t

3
50 10
10 20
20 5

Output O u t p u t

3500

思路:
考试时这道题是第一题,推了半天没搞懂,样例也看不懂。总以为正确答案是 50×10×20×5=50000 50 × 10 × 20 × 5 = 50000
但是并不是这样的。
我们设第 2 2 n+1行的输入数据为 a[i] a [ i ] b[i] b [ i ] ,那么我们必须先证明 b[i]=a[i+1] b [ i ] = a [ i + 1 ]

证明:
根据矩阵乘法的性质,矩阵A*矩阵B 不等于 矩阵B*矩阵A,那么我们能确定,A*B*C不等于A*C*B,
那么题目又说输入确保能够相乘,所以b[i]=a[i+1]。
证毕。

那么再根据 A(m×n)×B(n×p)=C(m×p) A ( m × n ) × B ( n × p ) = C ( m × p ) ,所以我们可以利用矩阵乘法可以用乘法结合律的特点,将 A(m×n)×B(n×p)×C(m×p) A ( m × n ) × B ( n × p ) × C ( m × p ) 变成 (A(m×n)×B(n×p))×C(m×p) ( A ( m × n ) × B ( n × p ) ) × C ( m × p ) ,两两相乘,就可以求出正确答案。

那么如何求最小值呢?

有两种方法求最小值,分别指区间DP和记搜,这里就介绍DP的方法。

可以用 f[i][j] f [ i ] [ j ] 表示矩阵 i i 到矩阵j都被合并的最小值,再从 i i j中枚举 k k ,将矩阵分为两边(类似Floyd思想, i i j最短路就枚举其他点 k k f[i][j]=min(f[i][j],f[i][k]+f[k][j])),再加上题目给出的 A(m×n)×B(n×p) A ( m × n ) × B ( n × p ) 要乘 m×n×p m × n × p 次,即 a[i]×a[k+1]×b[j] a [ i ] × a [ k + 1 ] × b [ j ] a[k+1]=b[k] a [ k + 1 ] = b [ k ] ,已经证过,所以也可以是 a[i]×b[k]×b[j] a [ i ] × b [ k ] × b [ j ] )。完整方程如下:

f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+a[i]*a[k+1]*b[j]);

f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+a[i]*b[k]*b[j]);

那么 i i j k k 分别该怎么枚举呢?

不难想到,由于是矩阵i到矩阵 j j ,所以必然有ij,所以 j j 就要从2枚举到n。(1不能枚举,因为如果枚举了1, i i 0,明显不成立)。那么根据 ij i ⩽ j ,也可以推出 i i 要从j1枚举到1。这里必须要倒着枚举,如果从1开始枚举的话, a[i][j] a [ i ] [ j ] 里面没有任何一个区间求出最小值,就无法转移。那么 k k 就很简单了,如上文所述,有ik j j ,所以k自然就在i j j 中枚举了。

那么还有最后一个问题:怎么初始化?

由于要求最小值,所以很容易想到要讲f数组全部赋值为 INF I N F ,但是。。。

f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+a[i]*a[k+1]*b[j]);

m=a[i]×a[k+1]×b[j] m = a [ i ] × a [ k + 1 ] × b [ j ] ,再带入方程,发现。。。

f[i][j]=min(INF,INF+INF+m)

f[i][j]=min(INF,INF)

f[i][j]=INF

怎么回事?

再仔细想一想, f[i][i] f [ i ] [ i ] 这个矩阵的值不可能是 INF I N F ,因为它是它本身,所以 f[i][i]=0 f [ i ] [ i ] = 0

那么当 i=j1 i = j − 1 k=i k = i 时,

f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+a[i]*a[k+1]*b[j]);

f[i][j]=min(INF,f[i][i]+f[i+1][j]+m);

f[i][j]=min(INF,f[i][i]+f[j][j]+m);

f[i][j]=min(INF,0+0+m);

f[i][j]=min(INF,m);

f[i][j]=m;

所以答案也出来了:从矩阵1乘到矩阵 n n ,即f[1][n]


代码:

#include <cstdio>
#include <iostream>
using namespace std;

const int inf=99999999;
int n,a[1001],b[1001],f[601][601];

int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
     scanf("%d%d",&a[i],&b[i]);
    for (int i=1;i<=600;i++)
     for (int j=1;j<=600;j++)  //初始化
      if (i!=j) f[i][j]=inf;
    for (int j=2;j<=n;j++)
     for (int i=j-1;i>=1;i--)
      for (int k=i;k<j;k++)
       f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+a[i]*a[k+1]*b[j]);  //方程不解释
    printf("%d\n",f[1][n]);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 给出一个$n\times m$的矩阵,每个位置上有一个非负整数,代表这个位置的海拔高度。一开始时,有一个人站在其中一个位置上。这个人可以向上、下、左、右四个方向移动,但是只能移动到海拔高度比当前位置低或者相等的位置上。一次移动只能移动一个单位长度。定义一个位置为“山顶”,当且仅当从这个位置开始移动,可以一直走到海拔高度比它低的位置上。请问,这个矩阵中最多有多少个“山顶”? 输入格式 第一行两个整数,分别表示$n$和$m$。 接下来$n$行,每行$m$个整数,表示整个矩阵。 输出格式 输出一个整数,表示最多有多少个“山顶”。 样例输入 4 4 3 2 1 4 2 3 4 3 5 6 7 8 4 5 6 7 样例输出 5 算法1 (递归dp) $O(nm)$ 对于这道题,我们可以使用递归DP来解决,用$f(i,j)$表示以$(i,j)$为起点的路径最大长度,那么最后的答案就是所有$f(i,j)$中的最大值。 状态转移方程如下: $$ f(i,j)=\max f(x,y)+1(x,y)是(i,j)的下一个满足条件的位置 $$ 注意:这里的状态转移方程中的$x,y$是在枚举四个方向时得到的下一个位置,即: - 向上:$(i-1,j)$ - 向下:$(i+1,j)$ - 向左:$(i,j-1)$ - 向右:$(i,j+1)$ 实现过程中需要注意以下几点: - 每个点都需要一遍,因此需要用双重for循环来枚举每个起点; - 对于已经索过的点,需要用一个数组$vis$来录,防止重复索; - 在进行状态转移时,需要判断移动后的点是否满足条件。 时间复杂度 状态数为$O(nm)$,每个状态转移的时间复杂度为$O(1)$,因此总时间复杂度为$O(nm)$。 参考文献 C++ 代码 算法2 (动态规划) $O(nm)$ 动态规划的思路与递归DP类似,只不过转移方程和实现方式有所不同。 状态转移方程如下: $$ f(i,j)=\max f(x,y)+1(x,y)是(i,j)的下一个满足条件的位置 $$ 注意:这里的状态转移方程中的$x,y$是在枚举四个方向时得到的下一个位置,即: - 向上:$(i-1,j)$ - 向下:$(i+1,j)$ - 向左:$(i,j-1)$ - 向右:$(i,j+1)$ 实现过程中需要注意以下几点: - 每个点都需要一遍,因此需要用双重for循环来枚举每个起点; - 对于已经索过的点,需要用一个数组$vis$来录,防止重复索; - 在进行状态转移时,需要判断移动后的点是否满足条件。 时间复杂度 状态数为$O(nm)$,每个状态转移的时间复杂度为$O(1)$,因此总时间复杂度为$O(nm)$。 参考文献 C++ 代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值