动态规划week7

 动态规划:加速计算,化指为积

动态规划题目特点:

1:计数

  • 有多少种方式到达终点
  • 多少种方法选出k个数使得其值为s

2:求最大值最小值

  • 最长上升子序列

3:存在性问题·:

  • 取石子游戏,先手是否必胜
  • 能否取出k个数值为s

动态规划(n*m)组成部分:

一:确定状态:

  • 最后一步(最优策略中使用的最后一步)
  • 化解子问题

二:转移方程:

  • f()=min(max){        }

三:初始条件和边界情况

  • 如数组下标不能为负
  • 起始的状态

四:计算顺序:

  • 从大到小(或者从小到大)

T1:采药

题目:

# [NOIP2005 普及组] 采药

## 题目描述

辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”


如果你是辰辰,你能完成这个任务吗?

## 输入格式

第一行有 $2$ 个整数 $T$($1 \le T \le 1000$)和 $M$($1 \le  M \le 100$),用一个空格隔开,$T$ 代表总共能够用来采药的时间,$M$ 代表山洞里的草药的数目。

接下来的 $M$ 行每行包括两个在 $1$ 到 $100$ 之间(包括 $1$ 和 $100$)的整数,分别表示采摘某株草药的时间和这株草药的价值。

## 输出格式

输出在规定的时间内可以采到的草药的最大总价值。

思路:

设置一个二维数组dp【i】【j】,其值代表当前状态下选取得到的最大价值,第一维度代表第i个物品,第二个维度代表当前状态下背包容量j。对于第i个物品i如果不挑选(或者背包容量不支持放下第i个物品)那么dp【i】【j】=dp【i-1】【j】。如果当前的背包容量支持放下第i个物品,那么就会产生一个状态转移:dp【i】【j】=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);

#include<bits/stdc++.h>
using namespace std;
int T,M,v[1005],w[1005];//v[]代表价值,w代表重量                                                                           
int dp[1005][10005]; 
int main()
{
	cin>>T>>M;
	for(int i=1;i<=M;i++){
		cin>>t[i]>>v[i];
	}
	for(int i=1;i<=M;i++){
		for(int j=1;j<=T;j++){
			dp[i][j]=dp[i-1][j]//背包容量不够的情况
			if(t[i]<=j) dp[i][j]=max(dp[i-1][j],dp[i-1][j-t[i]]+v[i]);//状态转移
		}
	}
	cout<<dp[M][T];
	return 0;
} 

T2:最长上升子序列

题目:

# 最长上升子序列

## 题目描述

这是一个简单的动规板子题。

给出一个由 $n(n\le 5000)$ 个不超过 $10^6$ 的正整数组成的序列。请输出这个序列的**最长上升子序列**的长度。

最长上升子序列是指,从原序列中**按顺序**取出一些数字排在一起,这些数字是**逐渐增大**的。

## 输入格式

第一行,一个整数 $n$,表示序列长度。

第二行有 $n$ 个整数,表示这个序列。

## 输出格式

一个整数表示答案。

思路:

这道题感觉和线性代数求逆序数有点类似只不过这个是反正来(求正序数最大值?)

用一个f[i]数组代表第i个数字对应的最大上升序列的个数,然后对于每一个数字它的最小的f[i]=1,就是它本身,对于每一个数字的正序数最大值怎么求呢?去前面找比它还小的数:状态转移:f[i]=max(f[i],f[j]+1),然后答案就是f【i】中的最大值;

#include<bits/stdc++.h>
using namespace std;
int n,a[10005],f[10005],ans=0;
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];f[i]=1;
	}
	
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<i;j++){//寻找前面比他小的数
			if(a[j]<a[i]){
				f[i]=max(f[i],f[j]+1);
			}
		} 
		ans=max(f[i],ans);
		//cout<<"f["<<i<<"]="<<f[i]<<endl;
	}
	cout<<ans<<endl;
	return 0;
}

T3:最大子段和

题目:

# 最大子段和

## 题目描述

给出一个长度为 $n$ 的序列 $a$,选出其中连续且非空的一段使得这段和最大。

## 输入格式

第一行是一个整数,表示序列的长度 $n$。

第二行有 $n$ 个整数,第 $i$ 个整数表示序列的第 $i$ 个数字 $a_i$。

## 输出格式

输出一行一个整数表示答案。

思路:

用一个dp【】数组储存当前第i个数字对应的最大·子段和,状态转移方程:dp【i】=max(dp[i-1]+a[i],a[i])(如果dp[i-1]是一个1负数,那么选择这个数本身a【i】显然更优)

#include<bits/stdc++.h>
using namespace std;
int n,dp[200005],a[200005];
int  main()
{
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	int ans=a[1];
	dp[1]=a[1]; 
	for(int i=2;i<=n;i++){
		dp[i]=max(dp[i-1]+a[i],a[i]);
		ans=max(ans,dp[i]);//答案就是dp数组中的最大值
	}
	cout<<ans<<endl;
	return 0;
}

T4:LCS

题目:

题目描述:

给定一个字符串 ss 和一个字符串 tt ,输出 ss 和 tt 的最长公共子序列。

输入格式:

两行,第一行输入 ss ,第二行输入 tt 。

输出格式:

输出 ss 和 tt 的最长公共子序列。如果有多种答案,输出任何一个都可以。

说明/提示:

数据保证 ss 和 tt 仅含英文小写字母,并且 ss 和 tt 的长度小于等于3000。

输入 #1复制

axyb
abyxb

输出 #1复制

axb

思路:

设dp【i】【j】为a(1到i),b(1到j)的LCS长度,然后对应的边界dp【0】【j】=dp【i】【0】=0,状态转移方程为:dp【i】【j】=max(dp【i】【j-1】,dp【i-1】【j-1】,dp【i-1】【j-1】+1),然后用一个path【】数组记录下转移的路径就可以输出答案了。

#include<bits/stdc++.h>
using namespace std;
#define pb push_back
const int N=3000,M=3000;
int n,m;
char a[N+5],b[M+5];
int dp[N+1][M+1],path[N+1][M+1]/*转移路径*/;
int main(){
	int n,m;
	cin>>a+1>>b+1;//让字符串下标从1开始
	n=strlen(a+1);m=strlen(b+1);
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){ 
		dp[i][j]=dp[i-1][j];path[i][j]=1;//第1种
		if(dp[i][j-1]>dp[i][j])dp[i][j]=dp[i][j-1],path[i][j]=2;//第2种 
		if(a[i]==b[j]&&dp[i-1][j-1]+1>dp[i][j])dp[i][j]=dp[i-1][j-1]+1,path[i][j]=3;//第3种 
	}
	int nowi=n,nowj=m;
	string ans; 
	while(nowi&&nowj) 
		if(pa[nowi][nowj]==1)nowi--;
		else if(pa[nowi][nowj]==2)nowj--;
		else ans+=a[nowi],nowi--,nowj--;
	for(int i=ans.size()-1;i>=0;i--)cout<<ans[i];
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值