动态规划

动态规划

动态规划是一种解决最优化的算法。动态规划将一个问题划分为若干个子问题。通过综合子问题的最优解来得到原问题的最优解。
动态规划会将子问题的解记录下来,当碰到同一个问题时,就会直接使用,而不是重复计算。
其中有递推和递归两种写法,其中递归写法又称为记忆化搜索。

数塔问题

传送门
思路:我们需要一个dp[i][j]存dp[i][j]对底层最大的数。
比如我们从上面到下面要访问一个数,如果我们不储存这个数,那么这个数会反复重复重复,所以要储存这个数。因此,我们要从下往上查询,记录下结果,以后直接使用就可以了 。
递推写法:

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=1e2+10;
int s[maxn][maxn];
int dp[maxn][maxn];
int n,t;
int main(){
	scanf("%d",&t);
	while(t--){
	scanf("%d",&n);
	memset(s,0,sizeof(s));
	memset(dp,0,sizeof(dp));
	for(int i=1;i<=n;i++)
	for(int j=1;j<=i;j++)
	scanf("%d",&s[i][j]);
	for(int i=n;i>=1;i--){
		for(int j=1;j<=i;j++){
			dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+s[i][j];
		}
	}
	printf("%d\n",dp[1][1]);		
	}
	return 0;
}

递归写法:
对于递归,我们会经历很多重复的步骤,这时候我们要剪枝并且记录。当dp[x][y] 不为0,我们就认为此时的dp[x][y]是这个位置及其以下最大点值

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e2+10;
int t;
int n,dp[maxn][maxn],s[maxn][maxn];
int solve(int x,int y){
	if(x==n) dp[x][y]=s[x][y];
    if(dp[x][y]!=0) return dp[x][y];
	else dp[x][y]=s[x][y]+max(solve(x+1,y),solve(x+1,y+1));
	return dp[x][y];
}
int main(){
	scanf("%d",&t);
	while(t--){
	scanf("%d",&n);
	memset(s,0,sizeof(s));
	memset(dp,0,sizeof(dp));
	for(int i=1;i<=n;i++)
	for(int j=1;j<=i;j++)
	scanf("%d",&s[i][j]);
	solve(1,1);
	printf("%d\n",dp[1][1]);		
	}
	return 0;
}

最大连续子序列和

P1115 最大子段和

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
int s[maxn],n;
int dp[maxn];
int sum=-0x3f3f3f3f;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%d",&s[i]);
	for(int i=1;i<=n;i++){
		dp[i]=max(dp[i-1]+s[i],s[i]);
	}
	for(int i=1;i<=n;i++){
		sum=max(dp[i],sum);
	}
	printf("%d\n",sum);
	return 0;
}

最长上升子序列(LIS)

在一个数字序列中,找到一个最长上升子序列(可以不连续)

O(n^2)
模板题

#include<iostream>
using namespace std;
const int maxn=5e3+10;
int dp[maxn],s[maxn],n,sum;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%d",&s[i]);
	for(int i=1;i<=n;i++)
	dp[i]=1;
	for(int i=2;i<=n;i++){
		for(int j=1;j<i;j++){
			if(s[i]>s[j]) dp[i]=max(dp[i],dp[j]+1);
		}
		sum=max(sum,dp[i]);
	}
	printf("%d\n",sum);
	return 0;
}

接下来,我们想办法优化他。
设 用 f[i] 表示所有数 ( a[i] 到 a[n] ) 组成的LIS中,所有长度为 i 的LIS的末尾元素中最小值
比如一个序列:3,5,1,4这一个序列。
我们用O(n^2)来表示
dp[1]=1
dp[2]=2;
dp[3]=2;
dp[4]=2;
我们可以看出,我们从头到尾比较其实是一种浪费,只需要比较最后一个即可。
我们再用O(nlogn)来表示
f[1]=3;
f[]={3,5}
f[]={1,5}
f[]={1,4}
来维护单调性。
利用二分替换掉第一个大于等于它的数

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1e5+10;
int s[maxn],n,f[maxn];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%d",&s[i]);
	int len=0;
	for(int i=1;i<=n;i++){
		if(s[i]>f[len]) f[++len]=s[i];
		else{
			int a=lower_bound(f+1,f+1+len,s[i])-f;
			f[a]=s[i];
		}
	}
	for(int i=1;i<=len;i++)
	cout<<f[i]<<" ";
	printf("%d\n",len);
}

还有一种是用线段树。

最长公共子序列(LCS)

给定两个字符串或者数字,求一个字符串,使得字符串是A和B最长公共部分。(子序列可以不连续)

做法: 令dp[i][j]表示第一个字符串第前个字符和第二个字符串前j个字符的LCS

我们进行两种决策:(状态方程)
(1)对于A[i]==B[j],我们进行dp[i-1][j-1]+1的操作
(2)对于A[i]!=B[j],我们进行dp[i-1][j]和dp[i][j-1]的最大值。即dp[i][j]=max(dp[i-1][j],dp[i][j-1])
边界: dp[i][0]=0||dp[0][j]=0
模板题

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=1e3+10;
string a,b;
int dp[maxn][maxn];
int main(){
	while(cin>>a>>b){
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=a.length();i++){
			for(int j=1;j<=b.length();j++){
				if(a[i-1]==b[j-1])
				dp[i][j]=dp[i-1][j-1]+1;
				else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
			}
		}
		printf("%d\n",dp[a.length()][b.length()]);
	}
	return 0;
}

最长回文子串

给出一个字符串s,求s的最长回文子串的长度。
dp[i][j]表示s[i]到s[j]所表示的子串是否是回文子串,是为1,不是为0
根据s[i]是否等于s[j],把转移情况分为两种:
(1)若s[i]==s[j],那么只要s[i+1]至s[j-1]是回文子串,s[i]至s[j]就是回文子串;如果s[i+1]至s[j-1]不是回文子串,那么s[i]至s[j]也不是回文子串。
(2)s[i]!=s[j],那么s[i]至s[j]一定不是回文子串。
由此转移方程:
dp[i][j]=if(s[i]==s[j]) dp[i+1][j-1]
else 0
边界 dp[i][i]=1

#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=1e3+10;
char s[maxn];
int dp[maxn][maxn];
int main(){
	gets(s);
	int len=strlen(s),ans=1;
	memset(dp,0,siezof(dp));
	for(int i=0;i<len;i++){
		dp[i][i]=1;
		if(i<len-1){
			if(s[i]==s[i+1]){
				dp[i][i+1]=1;
				ans=2;
			}
		}
	}
	for(int L=3;L<=len;L++){
		for(int i=0;i+L-1<len;i++){
			int j=i+L-1;
			if(s[i]==s[j]&&dp[i+1][j-1]==1){
				dp[i][j]=1;
				ans=L;
			}
		}
	}
	printf("%d\n",ans);
	return 0;
}

数塔DP

DAG最长路

DAG就是有向无环图。
DAG最长路和DAG最短路道理相同。
有两个问题:
(1)求整个路径的最长路(既不固定起点和终点)
(2)固定终点,求DAG最长路经
我们先讨论第一个问题
我们令dp[i]表示从i点出发的最长的路径,
如果dp[j1]、dp[j2]、dp[j3]均已知,那么就有dp[i]=max(dp[j]+length(i->j))

待续

01背包

传送门

完全背包

传送门

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值