week 7动态规划

P1048 [NOIP2005 普及组] 采药

题目描述

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

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

输入格式

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

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

输出格式

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

输入输出样例

输入 #1

70 3
71 100
69 1
1 2

输出 #1 

很明显的一题动态规划题,看上去和经典例题01背包是一模一样的。

所以我们直接按照01背包的写法得出答案即可

#include<bits/stdc++.h>
using namespace std;
int a[105][2];
int b[1005][1005];
int t,m; 
int main(){
	cin>>t>>m;
	for(int i=1;i<=m;i++){
		cin>>a[i][0]>>a[i][1];//0是时间,1是价值; 
	}
	for(int i=1;i<=m;i++){
		for(int j=1;j<=t;j++){
			if(j>=a[i][0]){//我的时间是够采这草药的 
				b[i][j]=max(b[i-1][j],b[i-1][j-a[i][0]]+a[i][1]);//装的下的话,比较谁是最优解 
			}
			else b[i][j]=b[i-1][j]; 
		}
	} 
	cout<<b[m][t];
	return 0;
} 

 B3637 最长上升子序列

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

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

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

输入格式

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

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

输出格式

一个整数表示答案。

输入输出样例

输入 #1

6
1 2 4 1 3 4

输出 #1

4 

最长上升子序列,也是一道很经典的题,对于这题,我们可以用一个表格来表示出来

依照案例

我们把他的最优解也就是每个位置的最长上升子序列写出来

1    2    3    1    3     4   输入

1    2    3    1     3    4  最长上升子序列

这样我们就可以得出答案为4

定义一个maxx来记录最大值

#include<bits/stdc++.h>
using namespace std;
int a[1000005];
int b[1000005];
int main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	int maxx=0;
	for(int i=1;i<=n;i++){
		b[i]=1;
		for(int j=1;j<i;j++){
			if(a[i]>a[j]){
				b[i]=max(b[i],b[j]+1);
				maxx=max(maxx,b[i]);
			}
		}
	}
	cout<<maxx;
} 

P1115 最大子段和 

题目描述

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

输入格式

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

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

输出格式

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

输入输出样例

输入 #1

7
2 -4 3 -1 2 -4 3

输出 #1

 4

 我们可以很容易看出样例里面的最大子段和就是3 -1 2这部分形成的4

但是数据如果多起来了,我们就很难直接看出,所以我们得去思考这题的思路

字段得连续且非空

我们可以用动态规划的最优解去解决,就是当字段长为1时,最大是多少,为2时,最大是多少,然后以此类推得出我们的最大字段和

或者,我们可以对于2,如果我们加上-4,会变小。不符合,对于-4的话,我们就得加上2和3,这样是一个合适的解,对于3,我们加上-1和-4都会变小,这个解不适合,对于-1,我们就得加上3和2;这样我们就能很明显得看出,我们可以把每个数,对于它左右两边的两个数进行判断如果加上去会让这个数变大也就是这两个数是正数,我们就可以直接加上这两个数,再对下面两个数判断,不行的话就不再进行加减,然后把这个最后的值储存再数组的这个位置上

两边判断的话,我们就得对开头和结尾两个元素进行特殊判断,对于下一个元素我们也可能会重复加上了直接这个数,所以我们可以试试单一边判断

对于左边判断,只要第一个元素特殊赋值,然后对于我们正在判断的元素,它后面的元素并没有参与判断,下次判断的时候也不会漏掉它;

#include<bits/stdc++.h>
using namespace std;
int a[200005];//储存 
int b[200005];//记录 
int main(){
	int n;
	int maxn=-123456789;//如果和为负数,0大于负数,所以这边不能给maxn赋值为0
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	b[1]=a[1];
	for(int i=2;i<=n;i++){
		b[i]=max(b[i-1]+a[i],a[i]);
		maxn=max(maxn,b[i]);
	}
	cout<<maxn;
	return 0;
} 

LCS 

题目描述:

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

输入格式:

两行,第一行输入 s ,第二行输入 t 。

输出格式:

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

说明/提示:

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

输入输出样例

输入 #1

axyb
abyxb

输出 #1 

axb

 输入 #2

aa
xayaz

 输出 #2

 aa

这题其实好像是 在acm里面的百题里面出现过,但是那个时候是用的直接暴力搜索就行

而且那个公共子序列得相邻且一模一样才算是公共子序列

这个看样例就是里面有这个元素就行了

拿样例1来说,我们输入axyb abyxb,有两种答案,axb,ayb他们的长度都为3

既然是最长公共子序列,我们就先一个一个找试试

axyb

abyxb

先找第一个元素a 对于a参与的子序列,我们再找下一个ax(ay,ab),在第二个字符串里面也可以找到ax

下一个axy这次在第二个字符串里面就找不到了,所以我们对于ax再取y下一个元素,axb,在第二个字符串里面,欸找到了axb,其实我们也可以想一个树状图来记录,里面最长的就是我们要的最长公共子序列,有点dfsbfs的味道。我们这边还是得用dp来写的

对于我们要dp的结果ij;如果两个字符串的i-1,j-1元素相同,那么它是i-1,j-1的中国元素再加上1得到的,不相同的话就是由i-1,j ;  i,j-1 两个里面最大的那个值得到的;在pd数字数组这边就有01背包的味道,对于我们要的答案字符数组,我们就得用dp数组里面得到的结果去得出字符数组

这题的难点就在于逆向用dp数组去得出ans数组

#include<bits/stdc++.h>
using namespace std;
char ans[3005]; 
int dp[3005][3005];//用来记录最长的公共子字符串的长度 
int main(){
	string a,b;
	cin>>a>>b;
	int la=a.size();
	int lb=b.size();
	for(int i=1;i<=la;i++){
		for(int j=1;j<=lb;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]); 
			}
		}
	}
	int num=dp[la][lb];//n表示最长公共子字符串的长度
	int laa=la,lbb=lb;
	while(num--){
		if(a[la-1]==b[lb-1]){
			ans[num]=a[la-1];
			la--;
			lb--;
		}
		else if(dp[la][lb]==dp[la-1][lb]){
			la--;
			num++;
		}
		else if(dp[la][lb]==dp[la][lb-1]){
			lb--;
			num++;
		}
	}
    for (int i = dp[laa][lbb]-1; i>=0; i--) {
      cout << ans[i];
    }
	return 0;
}

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值