dp入门(线性dp)

动态规划(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 1x1,x2,x3,x4...xmn
与子集和字串的区别:
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,1in,dpi=max1j<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,jZ
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(dpi1,j,dpi,j1)dpi1,j1+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(dpi1,j,dpi,j1)max1k<j&&bk<bi{dpi1,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;
}
输出:参见最长公共子序列的输出方式,几乎一模一样
优化

还未发现

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值