矩阵最优连乘:
Description
给定n个矩阵{A0,A1,…,An-1}, 其中Ai,i=0,…,n-1的维数为pi*pi+1,并且Ai与Ai+1是可乘的。考察这n个矩阵的连乘积A0A1…An-1,由于矩阵乘法满足结合律,所以计算矩阵的连乘可有许多不同的计算次序。矩阵连乘问题是确定计算矩阵连乘积的计算次序,使得按照这一次序计算矩阵连乘积,需要的“数乘”次数最少。Input第一行输入n的值,第二行输入n个矩阵的维数pi(i=0,…,n)。Output最少乘法次数。
Sample Input
6
30 35 15 5 10 20 25
Sample Output
15125
Source
矩阵连乘算法算是经典的动态规划算法的问题(Dynamic Programming)(明显的具有最优子结构和重叠子问题的特点,证明略)。既然是动态规划问题,那很明显是用到了数组来存储子结构的解。
我们这里主要用到两个数组,一个是p数组,这是一维数组,存储着矩阵的维数,因为相邻两边的矩阵维数默认相同,对于n个矩阵来说,只需n+1个空间即可存储。
还有一个是m数组,这是一个二维数组m,m[i][j]是表示第i个矩阵和第j个矩阵相乘的计算次数。那么m[i][i]理所当然就是0啦,因为是自己乘自己。
如果i不等于j的话,我们就来一个递归的操作:设一个中间值k,i<=k<j,则m[i][j]=min{m[i][k]+m[k+1][j]+p[i]*p[k+1]*p[j+1]}(因为这个值是不停地比较产生的,所以需要去所有情况的最小值)这样就形成了一个类似于上三角的矩阵来表示递推计算次序:
#include
using namespace std;
const int N=7;
//p为矩阵链,p[0],p[1]代表第一个矩阵的行数和列数,p[1],p[2]代表第二个矩阵的行数和列数…length为p的长度
//所以如果有六个矩阵,length=7,m为存储最优结果的二维矩阵,s为存储选择最优结果路线的
//二维矩阵
void MatrixChainOrder(int *p,int m[N][N],int s[N][N],int length)
{
int n=length-1;
int l,i,j,k,q=0;
//m[i][i]只有一个矩阵,所以相乘次数为0,即m[i][i]=0;
for(i=1;i<length;i++)
{
m[i][i]=0;
}
//l表示矩阵链的长度
// l=2时,计算 m[i,i+1],i=1,2,...,n-1 (长度l=2的链的最小代价)
for(l=2;l<=n;l++)
{
for(i=1;i<=n-l+1;i++)
{
j=i+l-1; //以i为起始位置,j为长度为l的链的末位,
m[i][j]=0x7fffffff;
//k从i到j-1,以k为位置划分
for(k=i;k<=j-1;k++)
{
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-1] << endl;
}
void PrintAnswer(int s[N][N],int i,int j)
{
if(i==j)
{
cout<<"A"<<i;
}
else
{
cout<<"(";
PrintAnswer(s,i,s[i][j]);
PrintAnswer(s,s[i][j]+1,j);
cout<<")";
}
}
int main()
{
int p[N]={30,35,15,5,10,20,25};
int m[N][N],s[N][N];
MatrixChainOrder(p,m,s,N);
PrintAnswer(s,1,N-1);
return 0;
}
括号匹配问题:
一开始用栈来做,判断是否匹配,不匹配则计数+1,计算出不匹配个数。这种算法处理不了很多数据。
1.首先,建立一个二位数组dp[][]来存储结果,dp[i][j]表示从S[i]到S[j]的最少添加括号个数。
2.那么首先可以把dp[i][j](i==j)的值存为1,因为一个字符必定需要添加一个括号。其他值先赋值为0。(阶段0)
3.然后,进行循环遍历,每阶段考虑k个长度的区间(k从1到总长),每次考虑i,j内的区间(i从0开始依次考虑),如果S[i]==S[j],说明他们是匹配的,dp[i][j]=dp[i+1][j-1](括号里面部分需要添加的最小括号数)
4.此外,该题还有分割情况(比如"[[[]]][[[]]]"需要分成两部分考虑),应依次考虑所有分割情况,如果进行了某种分割后,最多需要添加括号更少,就采用更小的值,所以有for(int k = i;k<j;k++)dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
5.全部处理完后,dp[0][length-1]即为S[0]S[length]也就是整个字符串需要添加的最小括号数。
又该题可以看出,动态规划实际遍历了所有可能结果,其中用二维数组保存一些结果,加快了运算速度。
代码:
#define R 105
#include<stdio.h>
#include<limits.h>
int length(char s[]){
int i=0;
while(s[i++]!='\0');
return i-1;
}
bool match(char a,char b){
if(a=='('&&b==')')return 1;
if(a=='['&&b==']')return 1;
return 0;
}
int min(int a,int b){
if(a<b)return a;
else return b;
}
int main(){
int n;
char all[R];
int dp[R][R];
int i,j,k,x;
int len;
scanf("%d",&n);
while(n--){
scanf("%s",all);
len = length(all);
for(i=0;i<len;i++)
for(j=0;j<R;j++)
dp[i][j]=0;
for(i=0;i<len;i++)
dp[i][i]=1;
for(k=1;k<=len;k++){//k为阶段数,表示处理长度为k的区间
for(i=0;i<len-k;i++){
j=i+k;
dp[i][j]=INT_MAX;
if(match(all[i],all[j]))
dp[i][j]=dp[i+1][j-1];
for(x=i;x<j;x++)
dp[i][j]=min(dp[i][j],dp[i][x]+dp[x+1][j]);
//printf("dp[%d][%d] = %d\n",i,j,dp[i][j]);
}
}
printf("%d\n",dp[0][len-1]);
}
return 0;
}
合并石子:(圆形)
代码:
#include <iostream>
using namespace std;
int n, num[105], sum[105] = {};
int dp[105][105], ans = 0x3f3f3f3f;;
int main()
{
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> num[i];
num[i + n] = num[i];
dp[i][i] = 0;
}
for (int i = 1; i <= n + n; ++i) {
sum[i] = sum[i - 1] + num[i];
}
for (int k = 2; k <= n; ++k) {
for (int i = 1; i <= 2*n - k + 1; ++i) {
int j = i + k - 1;
dp[i][j] = 0x3f3f3f3f;
for (int t = i; t < j; ++t) {
dp[i][j] = min(dp[i][j], dp[i][t] + dp[t + 1][j] + sum[j] - sum[i - 1]);
}
}
}
for (int i = 1; i <= n; ++i) {
ans = min(ans, dp[i][i + n - 1]);
}
cout << ans;
}