动态规划(Dynamic programming)
适用题型
1.无后效性:当前阶段在后面阶段的决策中不会受到更改
2.最优子结构:即当前阶段的决策一定是前面阶段的决策最优的情况下的决策
3.有重复子问题*:对于某些基础状态变量和阶段变量,会重复使用
*:不一定是适用范围,但一定是优势所在
对于基本定义的理解(不标准…)
1.阶段:当前处理的地方,处理的变量叫"阶段变量"
2.状态:当前已有的自然变量和客观变量为状态,这些叫做状态变量
3.决策:如何处理当前阶段
线性dp
“定义”:
处理过程像一条线一样的dp
题型1:最长上升子序列(LIS)
一个一个解释
最长
长度最大
上升
对于 ∀ i , j \forall i,j ∀i,j, i < j i<j i<j且 a i < a j a_i<a_j ai<aj
子序列
对于任意序列a={a1,a2,a3,…,an}
删去任意几个位置上的数,留下来的a’={ax1,ax2,ax3,…,axm}
1
≤
x
1
,
x
2
,
x
3
,
x
4...
x
m
≤
n
1\le x1,x2,x3,x4...xm\le n
1≤x1,x2,x3,x4...xm≤n
与子集和字串的区别:
1.子集(a’’):无序,a’
∈
\in
∈a’’
2.子串(a’’):原串a的k个连续元素
3.子序列:有序,但不连续在原串里出现
思路
dpi:以下标为i的元素结尾的LIS
对于
∀
d
p
i
,
1
≤
i
≤
n
,
d
p
i
=
m
a
x
1
≤
j
<
i
&
&
a
j
<
a
i
{
d
p
j
}
\forall dp_i,1\le i\le n,dp_i=max_{1\le j<i\&\&a_j<a_i}\{dp_j\}
∀dpi,1≤i≤n,dpi=max1≤j<i&&aj<ai{dpj}
解释
对于ai,在它之前的且它能接上的LIS,求最大值
注意
1:LIS不一定从第一位开始(每个赋初值1)
2:LIS不一定从第一位结束(每个dp元素都求一遍max)
代码
#include <bits/stdc++.h>
using namespace std;
int n,a[1005],dp[1005];
int main() {
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
}
int Max=0;
for(int i=1;i<=n;++i){
dp[i]=1;
for(int j=i;j>=0;--j){
if(a[i]>a[j]){
dp[i]=max(dp[j]+1,dp[i]);
}
}
Max=max(Max,dp[i]);
}
cout << Max;
return 0;
}
输出序列
利用链表,找到最前的,最大的,能接上的位置,用下标or指针连上,递归
(就不给代码了~~)
优化
将dpi的定义改一下:长度为i的LIS的结尾的最小值(自行理解)
这样,实现就很简单了
(就不给代码了~~~)
应用:导弹拦截
这里只是最长不下降子序列,一样的
题型2:最长公共子序列(LCS)
∀ a = { a 1 , a 2 , a 3 , a 4 . . . , a n } b = { b 1 , b 2 , b 3 , b 4 . . . , b m } \forall a=\{a_1,a_2,a_3,a_4...,a_n\} b=\{b_1,b_2,b_3,b_4...,b_m\} ∀a={a1,a2,a3,a4...,an}b={b1,b2,b3,b4...,bm}
一个一个解释
公共
两个原序列都有的元素
∀
i
∈
[
1
,
n
]
,
∀
j
∈
[
1
,
m
]
i
,
j
∈
Z
\forall i\in [1,n],\forall j\in [1,m]~~~~i,j\in Z
∀i∈[1,n],∀j∈[1,m] i,j∈Z
a
i
=
b
j
a_i=b_j
ai=bj
思路
∀
i
∈
[
1
,
n
]
,
j
∈
[
1
,
m
]
\forall i\in [1,n],j\in [1,m]
∀i∈[1,n],j∈[1,m]
d
p
i
,
j
:
a
dp_{i,j}:a
dpi,j:a序列 1-i,
b
b
b序列1-j ,这两段的LCS的长度,规定以bj结尾
d
p
i
,
j
=
{
m
a
x
(
d
p
i
−
1
,
j
,
d
p
i
,
j
−
1
)
a
i
!
=
b
j
d
p
i
−
1
,
j
−
1
+
1
a
i
=
=
b
j
dp_{i,j}=\left\{ \begin{aligned} max(dp_{i-1,j},dp_{i,j-1})&&a_i!=b_j\\ dp_{i-1,j-1}+1&&a_i==b_j \end{aligned} \right.
dpi,j={max(dpi−1,j,dpi,j−1)dpi−1,j−1+1ai!=bjai==bj
代码
#include<bits/stdc++.h>
using namespace std;
char a[1005],b[1005];
int dp[1005][1005];
int main() {
scanf("%s\n%s",a,b);
for(int i=0;i<strlen(a);++i){
for(int j=0;j<strlen(b);++j){
if(a[i]==b[j]) dp[i+1][j+1]=dp[i][j]+1;
else dp[i+1][j+1]=max(dp[i+1][j],dp[i][j+1]);
}
}
printf("%d",dp[strlen(a)][strlen(b)]);
return 0;
}
输出序列
还是用链表~~~
这里有三种状态转移方式,而且dp是二维的
所以,pre数组存转移状态的方式
(代码有点丑,就不放了)
题型3:最长公共上升子序列(LCIS)
就不解释了(都有)
思路
先满足公共,再满足上升
状态转移方程:
d
p
i
,
j
=
{
m
a
x
(
d
p
i
−
1
,
j
,
d
p
i
,
j
−
1
)
a
i
!
=
b
j
m
a
x
1
≤
k
<
j
&
&
b
k
<
b
i
{
d
p
i
−
1
,
k
}
+
1
a
i
=
=
b
j
dp_{i,j}=\left\{ \begin{aligned} max(dp_{i-1,j},dp_{i,j-1})&&a_i!=b_j\\ max_{1\le k<j\&\&b_k<b_i}\{dp_{i-1,k}\}+1&&a_i==b_j \end{aligned} \right.
dpi,j={max(dpi−1,j,dpi,j−1)max1≤k<j&&bk<bi{dpi−1,k}+1ai!=bjai==bj
认真看
代码
#include <bits/stdc++.h>
using namespace std;
int n,a[3005],b[3005],dp[3005][3005];
int main() {
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
}
for(int i=1;i<=n;++i){
scanf("%d",&b[i]);
}
int Max=0;
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
if(a[i]!=b[j]) dp[i][j]=dp[i-1][j];
else{
dp[i][j]=1;
for(int k=1;k<j;++k){
if(b[k]<b[j])
dp[i][j]=max(dp[i][j],dp[i-1][k]+1);
}
}
Max=max(Max,dp[i][j]);
}
}
cout << Max;
return 0;
}
输出:参见最长公共子序列的输出方式,几乎一模一样
优化
还未发现