矩阵连乘

代码其实……有点麻烦

详细解读

https://blog.csdn.net/qq_19782019/article/details/94356886

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// http://www.nbuoj.com/v8.83/Contests/Problem.php?cid=6743&nid=1  
#include <iostream>
#include <algorithm>
using namespace std;

//输入矩阵的行列
int arr[2333];
//m用来记录计算量
int m[2333][2333]={0};
//s用来保存括号位置
int s[2333][2333]={0};
//n是矩阵个数
int n;

int main()
{
cin >> n;
for (int i=0;i<=n;i++)
cin >>arr[i];
//r记录规模,从规模为2开始计算,如AB;r=3时,ABC,r=4,ABCD
//ABCD都指一个矩阵
for (int r=2;r<=n;r++){
//n-r+1 表示从某个矩阵最多后面要连接多少个其它矩阵
//如A后面最多连接3个,B后面最多连接2个,C后面最多连接1个
for (int i=1;i<=n-r+1;i++){
//j的意思是,从i后面开始
int j=i+r-1;
//对m[i][j]初始值进行设置
//假设现在要连接D,让它假设为,直接连接到ABC后面,形成ABCD,先不管ABC是怎么连接的
//ABC可能为A(BC)或(AB)C
m[i][j]=m[i+1][j]+arr[i-1]*arr[i]*arr[j];
//s[i][j]是用来保存“括号”位置的,这里没用上
s[i][j]=i;
//k是在i~j之间,表示“括号”插入的位置
for(int k=i+1;k<j;k++){
// temp记录,如果将“括号”插入在不同位置,再连乘后的计算量
// m[i][k]是A[i:k]的计算量,A泛指某个矩阵
// m[k+1][j]是A[k+1,j]的计算量
// arr[i-1]*arr[k]*arr[j]是 A[i:k]乘以A[k+1,j] 的计算量
int temp=m[i][k]+m[k+i][j]+arr[i-1]*arr[k]*arr[j];

// 如果用这个算法得出的计算量temp,比“直接连接”的方法的计算量(刚刚初始的m[i][j])要少,则更新
// 如果括号插入在不同位置,计算量更少,也会更新
if(m[i][j]>temp)
{
m[i][j]=temp;
s[i][j]=k;//记录括号的位置,这里没用上
}
}
}
}
cout << m[1][n]<<endl;
return 0;
}

举例

A=50*10,B=10*40,C=40*30,D=30*50

A B C D
A 0 AB 50*10*40 A(BC) 27000 A(B(CD)) 10500
B 0 BC 10*40*30 12000 B(CD) 8000
C 0 CD 40*30*5 6000
D 0

从表格的左上开始,先AB,BC,CD,再[ABC],[BCD],再[ABCD],中括号[]只是表示矩阵连乘的规模,不是顺序

  1. m[A][B]=m[B][B]+50*10*40 形成AB
  2. m[B][C]=m[C][C]+10*40*30 形成BC
  3. m[A][C]=min{ m[A][A] 0 +m[B][C] 12000 +50*10*30 1500027000 相当于:A(BC),m[A][B] 20000 +m[C][C] 0 +50*40*30 6000080000 相当于:(A)BC}
  4. m[A][C] 选择两个中小的,形成A(BC)

……

……

  1. m[A][D]=min{ m[A][A] 0 +m[B][D] 8000 +50*10*50 2500033000 相当于:A[B~D] ,m[A][B] 20000 +m[C][D] 6000 +50*40*50 100000126000 相当于:[AB][CD] ,m[A][C] 27000 +m[D][D] 0 +50*30*50 75000102000 相当于: [A~C]D }
  • 上面的[B~D]其实也会选择最小值,变成B(CD)
  • 上面的[A~C]选择最小值,变成A(BC)
  • 所以对m[A][D]最小的是A[B~D]也就是A(B(CD))


三角路径最大

题目

image-20220609223701986

解析

我们不妨将金字塔倒过来

1
2
3
4
5
6
[     
[4,1,8,3],
[6,5,7],
[3,4],
[2]
]

我们只要再相邻的元素中选出最大的值,然后往对应的下一层加即可。

例如[4,1,8,3]41比,所以我们将4加到下一层的6。接着,考虑18,我们将8加到下一层的5,接着考虑83,我们将8加到7,以此类推下去。

基于这个思想我们可以写出这样的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include<iostream>  
#include<algorithm>
using namespace std;
int n;
int *the_max;
int arr[1005][1005]={0};
int main()
{
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
cin>>arr[i][j];
}
}
the_max=arr[n];
//从倒数第一行倒序查找
for(int i=n;i>=2;i--){
//从第一列查找
for(int j=1;j<=i-1;j++){
//n-1行的值A加上A下方的两个值中最大者
arr[i-1][j]+=max(arr[i][j],arr[i][j+1]);
}
}
// res
cout<<arr[1][1]<<endl;
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
cout<<arr[i][j]<<" ";
}
cout<<endl;
}
}


01背包

题目

野地里有许多不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。要求在规定的时间t里,采到的草药的总价值最大。

  • 这里的总时间,就是背包总容量
  • 每个草药需要的时间,就是每个物品需要的容量

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int time,herb,tcost[200],hvalue[200];
int f[2000];
int i,j;
int main() {
//总时间,总草药数目
scanf("%d %d",&time,&herb);
//每个草药需要的时间和价值
for(i=0;i<herb;i++)
scanf("%d %d",&tcost[i],&hvalue[i]);
for(i=0;i<herb;i++)
//肯定从最大背包容量开始装
for( j=time;j>=tcost[i];j--)
//不采,和采 中选择其中价值最大的
//f[j]表示,在容量最大为"j"的情况下,能装的最大价值
f[j]=max(f[j],f[j-tcost[i]]+hvalue[i]);
printf("%d\n",f[time]);
return 0;
}

最长的非连续子序列

题目

在一个数字序列中,找到一个最长的非连续子序列,使得这个子序列是不下降(非递减)。现有序列A={1,2,3,-1,-2,7,9},则A的最长不下降子序列是{1,2,3,7,9}。

如果有多个最长序列,只需选数字顺位靠后的序列从大到小输出。

输入

7

31 96 27 -35 46 -96 0

输出

2

0 -96

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <iostream>  
#define themax(a,b) a>b?a:b
using namespace std;

int main() {
int n,res_index;
int arr[105]={0};
int dp[105]={0};
//记录结果路径的数组
int res_path[1005]={0};
cin>>n;
for(int i=0; i<n; i++) {
cin>>arr[i];
res_path[i]=0;
//dp初始设置为1,表示自身至少可以算一个
dp[i]=1;
}
int res=-1;
for(int i=1; i<n; i++) {
//j可以从0开始,但递减开始的话,需要修改dp[i]的次数会更少些
for(int j=i-1; j>=0; j--) {
//如果前一个数arr[j]不大于arr[i]而且,0到第j个数的连续不下降个数再加1(加1表示算上i后) 大于 0到第i个数的连续不下降个数
//则可以将第j个数的下一个连续数是i
if(arr[i]>=arr[j]){
//第i个元素记录的值前一个元素的下标j
//注意,要先判断是否来自于j,否则就会res_path[i]=i这种自己引入到自己……无意义
if(dp[j]+1>dp[i]){
res_path[i]=j;
}
//dp[i]记录0到第i个数的连续不下降个数
dp[i]=themax(dp[j]+1,dp[i]);
}
/*
上面的写法,不如直接把判断条件拿上来
if(arr[i]>=arr[j]&&dp[j]+1>dp[i]){
res_path[i]=j;
dp[i]=dp[j]+1;
}
*/
}
res=max(res,dp[i]);
}
//找到结果序列的首元素下标
for(int i=n-1; i>=0; i--) {
if(res==dp[i]) {
res_index=i;
break;
}
}
cout<<res<<endl;
//输出路径
for(int i=n-1; i>=n-res+1; i--) {
cout<<arr[res_index]<<" ";
res_index=res_path[res_index];
}
cout<<arr[res_index]<<endl;
}



最长公共子序列

题目与证明

https://blog.csdn.net/weixin_40522938/article/details/111223435

  • 当Xm=Yn时,有
    • Zk=Xm=Yn,Xm和Yn的LCS等于X(m-1)和Y(n-1)的LCS,状态1
  • 当Xm!=Yn时,有
    • Zk!=Xm,Xm和Yn的LCS等于Xm-1和Yn的LCS,状态2
    • Zk!=Yn,Xm和Yn的LCS等于Xm和Yn-1的LCS,状态3
    • 以上两种情况包含了Xm!=Yn时所有可能的情况,因为所求的LCS必然为以上两种情况中的一种,又因为求的是最长,所以所求LCS = Max(LCS2,LCS3);

所以LCS的转移方程为

  • c[i][j]={0,i> 0 ; j=0c[i1][j1]+1,i,j>0;  Xi=Yjmax{c[i][j1],c[i1][j]},i,j>0; Xi!=Yjc[i][j] = \begin{cases} 0, & \text{i> }0\text{ ; j=0} \\ c[i-1][j-1]+1, & \text{i,j>0; } \text{ Xi=Yj} \\ max\{c[i][j-1],c[i-1][j] \}, & \text{i,j>0;} \text{ Xi!=Yj} \end{cases}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//如果只求最大值
if(x[i]=y[j]){
c[i][j]=c[i-1][j-1]+1;
}
else{
c[i][j]=max(c[i-1][j],c[i][j-1]);
}


//如果需要记录是哪个字符,新增一个b数组
if(x[i]=y[j]){
c[i][j]=c[i-1][j-1]+1;
//b数组用来记录是哪些符号,如果只要求最大值,没必要有b数组
//状态1
b[i][j]=1;
}
else if(c[i-1][j]>c[i][j-1]){
//表示X(m-1)Y比XY(n-1)更大
c[i][j]=c[i-1][j];
//状态2
b[i][j]=2;
}
else{
//表示XY(n-1)比X(m-1)Y更大
c[i][j]=c[i][j-1];
//状态3
b[i][j]=3;
}

/*
b数组是上面的函数用来记录状态的
*/
void LCS(int i,int j,char *x,int **b){
if(i==0||j==0)
return 0;
if(b[i][j]==1){
//状态1
LCS(i-1,j-1,x,b);
//输出x,y相同的字符
cout<<x[i];
}
else if(b[i][j]==2){
//状态2,说明与X(m-1)Y一样
LCS(i-1,j,x,b);
}else{
//状态3,说明与XY(n-1)一样
LCS(i,j-1,x,b);
}
}

最大子段和

子段要求连续

输入 -2,11,-4,13,-5,-2 输出20;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int MaxSum(int n,int *arr){
//theMax是当前累积最大值
//b是前几个数
int theMax=0,b=0;
for(int i=0;i<n;i++){
//一旦前面几个数的和小于0,则抛弃前面的
//如 11 2 4 -100 20.读头到20时,b已经小于0了,b直接抛弃原来值并令b=20
if(b<=0)
b=arr[i];
else{
b+=arr[i];
}
if(b>theMax)
theMax=b;
}
}