目录
动态规划
设计动态规划算法的步骤
(1)找出最优解的性质,并刻画其结构特征。
(2)递归地定义最优值(写出动态规划方程)。
(3)自底向上计算出最优值。
(4)根据计算最优值时得到的信息,构造最优解。
动态规划问题的特征
(1)最优子结构:当问题的最优解包含其子问题的最优解时,则称该问题具有最优子结构。
(2)子问题重叠:有些子问题会被反复计算,动态规划利用该性质,每个子问题只解一次,而后将解保存在一个表格中。
一、矩阵连乘积问题
计算三个矩阵ABC的成绩,由于矩阵乘法的性质,不同计算顺序导致的乘法运算量可能相差悬殊。即(AB)C 和A(BC)的运算量差距很大。
详细解析如下:
https://blog.csdn.net/m0_74316795/article/details/139521336?spm=1001.2014.3001.5502
#include<bits/stdc++.h>
using namespace std;
#define NUM 51
int p[NUM];
int m[NUM][NUM];
int s[NUM][NUM];
int LookupChain(int i,int j)
{
if(m[i][j]>0)return m[i][j];
if(i==j)return 0;
int u = LookupChain(i,i)+LookupChain(i+1,j)+p[i-1]*p[i]*p[j];
s[i][j] = i;
for(int k=i+1;k<j;k++)
{
int t = LookupChain(i,k)+LookupChain(k+1,j)+p[i-1]*p[k]*p[j];
if(t<u){u=t;s[i][j]=k;}
}
m[i][j]=u;
return u;
}
void TraceBack(int i,int j)
{
if(i==j)printf("A%d",i);
else
{
printf("(");
TraceBack(i,s[i][j]);
TraceBack(s[i][j]+1,j);
printf(")");
}
}
int main()
{
int n;
scanf("%d",&n);
int i,temp;
for(i=0;i<=n-1;i++)
scanf("%d%d",&p[i],&temp);
p[n]=temp;
memset(m,0,sizeof(m));
LookupChain(1,n);
printf("%d\n",m[1][n]);
TraceBack(1,n);
return 0;
}
input:
6
50 10
10 40
40 30
30 5
5 20
20 15
output:
15750
((A1(A2(A3A4)))(A5A6))
二、最长公共子序列(LCS)
X=(A,B,C,B,D,A,B),Y=(B,D,C,A,B,A),则Z1=(B,C,B,A),Z2=(B,C,A,B),Z3=(B,D,A,B)均属于X,Y的最长公共子序列。
数组c用来记录最长公共子序列的长度,数组b记录c[i][j]的值是由哪一个子问题的解得到的。X,Y的最长公共子序列的长度记录在c[m][n]中。
for(i=1;i<=m;i++)c[i][0]=0;
for(i=1;i<=n;i++)c[0][i]=0;
0 | 1 | 2 | 3 | 4 | 5 | 6 | ||
Y | B | D | C | A | B | A | ||
0 | X | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | A | 0 | ||||||
2 | B | 0 | ||||||
3 | C | 0 | ||||||
4 | B | 0 | ||||||
5 | D | 0 | ||||||
6 | A | 0 | ||||||
7 | B | 0 |
//数组c初始化
for(i=1;i<=m;i++)
{
for(j=1;j<=n;j++)
{
if(x[i]==y[j])
{c[i][j]=c[i-1][j-1]+1;b[i][j]=1;}
else if(c[i-1][j]>=c[i][j-1])
{c[i][j]=c[i-1][j];b[i][j]=2;}
else {c[i][j]=c[i][j-1];b[i][j]=3;}
}
}
有相同元素时左上角+1;↖+1
没有相同元素时:
1.上面的值>=左面,取↑
2.否则取←
i=1,j=1时A与B不同,取上方元素
0 | 1 | 2 | 3 | 4 | 5 | 6 | ||
Y | B | D | C | A | B | A | ||
0 | X | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | A | 0 | 0↑ | |||||
2 | B | 0 | ||||||
3 | C | 0 | ||||||
4 | B | 0 | ||||||
5 | D | 0 | ||||||
6 | A | 0 | ||||||
7 | B | 0 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | ||
Y | B | D | C | A | B | A | ||
0 | X | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | A | 0 | 0↑ | 0↑ | ||||
2 | B | 0 | ||||||
3 | C | 0 | ||||||
4 | B | 0 | ||||||
5 | D | 0 | ||||||
6 | A | 0 | ||||||
7 | B | 0 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | ||
Y | B | D | C | A | B | A | ||
0 | X | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | A | 0 | 0↑ | 0↑ | 0↑ | |||
2 | B | 0 | ||||||
3 | C | 0 | ||||||
4 | B | 0 | ||||||
5 | D | 0 | ||||||
6 | A | 0 | ||||||
7 | B | 0 |
元素相同时,↖+1
0 | 1 | 2 | 3 | 4 | 5 | 6 | ||
Y | B | D | C | A | B | A | ||
0 | X | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | A | 0 | 0↑ | 0↑ | 0↑ | 1↖ | ||
2 | B | 0 | ||||||
3 | C | 0 | ||||||
4 | B | 0 | ||||||
5 | D | 0 | ||||||
6 | A | 0 | ||||||
7 | B | 0 |
左>上,取左边元素。
0 | 1 | 2 | 3 | 4 | 5 | 6 | ||
Y | B | D | C | A | B | A | ||
0 | X | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | A | 0 | 0↑ | 0↑ | 0↑ | 1↖ | 1← | |
2 | B | 0 | ||||||
3 | C | 0 | ||||||
4 | B | 0 | ||||||
5 | D | 0 | ||||||
6 | A | 0 | ||||||
7 | B | 0 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | ||
Y | B | D | C | A | B | A | ||
0 | X | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | A | 0 | 0↑ | 0↑ | 0↑ | 1↖ | 0← | 1↖ |
2 | B | 0 | ||||||
3 | C | 0 | ||||||
4 | B | 0 | ||||||
5 | D | 0 | ||||||
6 | A | 0 | ||||||
7 | B | 0 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | ||
Y | B | D | C | A | B | A | ||
0 | X | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | A | 0 | 0↑ | 0↑ | 0↑ | 1↖ | 0← | 1↖ |
2 | B | 0 | 1↖ | |||||
3 | C | 0 | ||||||
4 | B | 0 | ||||||
5 | D | 0 | ||||||
6 | A | 0 | ||||||
7 | B | 0 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | ||
Y | B | D | C | A | B | A | ||
0 | X | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | A | 0 | 0↑ | 0↑ | 0↑ | 1↖ | 0← | 1↖ |
2 | B | 0 | 1↖ | 1← | 1← | 1↑ | 2↖ | 2← |
3 | C | 0 | ||||||
4 | B | 0 | ||||||
5 | D | 0 | ||||||
6 | A | 0 | ||||||
7 | B | 0 |
数组c:LCS长度计算结果
0 | 1 | 2 | 3 | 4 | 5 | 6 | ||
Y | B | D | C | A | B | A | ||
0 | X | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | A | 0 | 0↑ | 0↑ | 0↑ | 1↖ | 0← | 1↖ |
2 | B | 0 | 1↖ | 1← | 1← | 1↑ | 2↖ | 2← |
3 | C | 0 | 1↑ | 1↑ | 2↖ | 2← | 2↑ | 2↑ |
4 | B | 0 | 1↖ | 1↑ | 2↑ | 2↑ | 3↖ | 3← |
5 | D | 0 | 1↑ | 2↖ | 2↑ | 2↑ | 3↑ | 3↑ |
6 | A | 0 | 1↑ | 2↑ | 2↑ | 3↖ | 3↑ | 4↖ |
7 | B | 0 | 1↖ | 2↑ | 2↑ | 3↑ | 4↖ | 4↑ |
最长公共子序列的构造:
在计算最长子序列长度的时候,我们用b[i][j]记录c[i][j]的值是由哪个子问题的解得到的。
b[i][j]==1↖;表示Xi与Yj的最长公共子序列是由Xi-1和Yj-1的最长公共子序列在尾部加上xi得到的子序列。
b[i][j]==2↑;表示Xi与Yj的最长公共子序列和Xi-1与Yj的最长公共子序列相同。
b[i][j]==3←;表示Xi与Yj的最长公共子序列和Xi与Yj-1的最长公共子序列相同。
void LCS(int i, int j, char x[])
{
if (i == 0 || j == 0) return;
if (b[i][j] == 1){LCS(i-1,j-1,x);printf("%c", x[i]);}
else if (b[i][j] == 2) LCS(i-1,j,x);
else LCS(i,j-1,x);
}
完整代码:
#include<bits/stdc++.h>
using namespace std;
#define NUM 105
int c[NUM][NUM];
int b[NUM][NUM];
char x[NUM];
char y[NUM];
void LCSLength(int m,int n,const char x[],char y[])
{
int i,j;
for(i=1;i<=m;i++)c[i][0]=0;
for(i=1;i<=n;i++)c[0][i]=0;
for(i=1;i<=m;i++)
{
for(j=1;j<=n;j++)
{
if(x[i]==y[j])
{c[i][j]=c[i-1][j-1]+1;b[i][j]=1;}
else if(c[i-1][j]>=c[i][j-1])
{c[i][j]=c[i-1][j];b[i][j]=2;}
else {c[i][j]=c[i][j-1];b[i][j]=3;}
}
}
}
/*
void LCS(int i, int j, char x[])
{
if (i == 0 || j == 0) return;
if (b[i][j] == 1){LCS(i-1,j-1,x);printf("%c", x[i]);}
else if (b[i][j] == 2) LCS(i-1,j,x);
else LCS(i,j-1,x);
}
*/
int main()
{
int m,n;
char x[NUM] = "ABCBDAB";
char y[NUM] = "BDCABA";
m = strlen(x);
n = strlen(y);
LCSLength(m,n,x,y);
for(int i=0;i<=m;i++)
{
for(int j=0;j<=n;j++)
{
printf("%d",c[i][j]);
}
printf("\n");
}
printf("%d\n",c[m][n]);
// LCS(m,n,x);
return 0;
}
运行结果:
0000000
0000111
0011111
0011222
0111222
0112233
0112333
0112334
4
三、最大字段和(连续不断开)
给定n个整数(可能有负数)组成的序列a1,a2,...,an,要在这n个数中选取相邻的一段ai,ai+1,...,aj(1<=i<=j<=n),使其和最大,并输出最大的和。当所有整数均为负数时,定义最大字段和为0。
1.计算最大字段和的动态规划算法
#include<bits/stdc++.h>
using namespace std;
#define NUM 1001
int a[NUM];
int MaxSum(int n)
{
int sum=0;
int b=0;
for(int i=1;i<=n;i++)
{
if(b>0)b+=a[i];
else b=a[i];
if(b>sum)sum=b;
}
return sum;
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
printf("%d\n",MaxSum(n));
return 0;
}
input:8
1 -3 7 8 -4 12 -10 6
output:23
2.计算动态字段和的动态规划算法(负数)
#include<bits/stdc++.h>
using namespace std;
#define NUM 1001
int a[NUM];
int MaxSum(int n)
{
int sum=a[1];
int b=a[1];
for(int i=2;i<=n;i++)
{
if(b>0)b+=a[i];
else b=a[i];
if(b>sum)sum=b;
}
return sum;
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
printf("%d\n",MaxSum(n));
return 0;
}
/*
input:8
1 -3 7 8 -4 12 -10 6
output:23
*/
3.最大字段和最优解
int MaxSum(int n,int &besti,int &bestj)
{
int sum=0;
int b=0;
int begin=0;//下标起始点从0开始
for(int i=1;i<=n;i++)
{
if(b>0)b+=a[i];//如果当前和大于0继续加
else {b=a[i];begin=i;};//若当前和小于0,将起点移动到现在i的位置
if(b>sum)//若当前和b大于sum
{
sum=b;//更新最大字段和的值
besti=begin;//若当前和小于0更新起点
bestj=i;//更新尾部
}
}
return sum;
}
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
a[i] | 1 | -3 | 7 | 8 | -4 | 12 | -10 | 6 |
b | 1 | -2 | 7 | 15 | 11 | 23 | 13 | 19 |
sum | 1 | 1 | 7 | 15 | 15 | 23 | 23 | 23 |
begin | 1 | 1 | 3 | 3 | 3 | 3 | 3 | 3 |
besti | 1 | 1 | 3 | 3 | 3 | 3 | 3 | 3 |
bestj | 1 | 1 | 3 | 4 | 5 | 6 | 6 | 6 |
#include<bits/stdc++.h>
using namespace std;
#define NUM 1001
int a[NUM];
int MaxSum(int n,int &besti,int &bestj)
{
int sum=0;
int b=0;
int begin=0;
for(int i=1;i<=n;i++)
{
if(b>0)b+=a[i];
else {b=a[i];begin=i;};
if(b>sum)
{
sum=b;
besti=begin;
bestj=i;
}
}
return sum;
}
int main()
{
int n;
int besti=0,bestj=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
printf("%d\n",MaxSum(n,besti,bestj));
printf("From %d to %d",besti,bestj);
return 0;
}
输入:
8
1 -3 7 8 -4 12 -10 6
输出:
23
From 3 to 6
四、最长单调递增子序列(可断开)
辅助数组b,定义b[i]表示以a[i](1<=i<=n)为结尾的最长递增子序列的长度,max{b[i]}为数组a最长递增子序列的长度。
k
(表示以a[i]
为结束的子序列长度)。
在当前值a[i]前面找一个最大值a[k],则以a[i]为结尾的最长递增子序列的长度就是b[k]+1。
序列L={65,158,170,155,239,300,207,389}
b[1]=1,初始化
b = 1
是因为单个元素本身就是其递增子序列
使用两个嵌套循环,外部循环从
i = 2
到n
,内部循环从j = 1
到i-1
。对于每个a[i]
,检查是否比之前的a[j]
大,并且当前的k
(表示以a[i]
为结束的子序列长度)小于b[j]
。如果是,则更新k
为b[j]+1
,因为我们可以将a[j]
添加到以a[i]
结尾的子序列中。更新
b[i]
为k
的值,同时记录下全局最大值maxa
,如果b[i]
大于maxa
,则更新maxa
。for(i=2;i<=n;i++){
int k=0;
for(j=1;j<=i-1;j++)//在当前坐标之前找
if(a[j]<a[i]&&k<b[j])k=b[j];
b[i]=k+1;
if(maxa<b[i])maxa=b[i];
}以i=2为例子:
for(j=1;j<=i-1;j++)
a[1]<a[2] k=0<b[1]=1,k=1,b[2]=k+1=2
i=3时
for(j=1;j<=2;j++)
a[1]<a[3] k=0<b[1]=1,k=1.
a[2]<a[3] k=1<b[2],k=b[2]=2
b[3]=k+1=3
i=4时
for(j=1;j<=3;j++)
a[1]<a[4] k=0<b[1]=1
a[2]>a[4]
a[3]>a[4]
b[4]=k+1=2
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
数组a | 65 | 158 | 170 | 155 | 239 | 300 | 207 | 389 |
数组b | 1 | 2 | 3 | 2 | 4 | 5 | 4 | 6 |
k | 0 | 1 | 2 | 1 | 3 | 4 | 3 | 5 |
maxa | 1 | 2 | 3 | 2 | 4 | 5 | 4 | 6 |
#include<bits/stdc++.h>
#define NUM 100
int a[NUM];
using namespace std;
int LIS_n2(int n)
{
int b[NUM]={0};
int maxa = 0;
int i,j;
b[1] = 1;
for(i=2;i<=n;i++)
{
int k=0;
for(j=1;j<=i-1;j++)
if(a[j]<a[i]&&k<b[j])k=b[j];
b[i]=k+1;
if(maxa<b[i])maxa=b[i];
}
return maxa;
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
printf("%d\n",LIS_n2(n));
return 0;
}
/*
8
65 158 170 155 239 300 207 389
output:6
*/
本博客资料、代码来源于清华大学出版社算法设计与分析,本博客仅用于个人学习,可能存在纰漏,敬请批评指正。