week7

本文介绍了如何使用动态规划解决最长上升子序列(LIS)和最长公共子序列(LCS)问题。对于LIS,从后向前遍历,根据当前元素与前一个元素的关系更新最大子序列长度;对于LCS,通过二维数组记录两个字符串中对应位置字符匹配的最大长度,最后反向回溯找出LCS。
摘要由CSDN通过智能技术生成

P1048 [NOIP2005 普及组] 采药

题目描述

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

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

输入格式

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

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

输出格式

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

输入输出样例

输入 #1

70 3
71 100
69 1
1 2

输出 #1

3

说明/提示

【数据范围】

  • 对于 30\%30% 的数据,M \le 10M≤10;
  • 对于全部的数据,M \le 100M≤100。

【题目来源】

NOIP 2005 普及组第三题

题解:

思路就是先建立重量数组和价值数组,将题目中的数据录进去,然后建立最优价值数组二维数组,行为每个药材的序号,列为重量,大小为从1到最大重量,然后通过比较将价值更大的药材存进去,

比如容量是70,第一个药材重量71,价值为100,显然重量从1-70都装不下这个药材,所以最优价值数组zy[1][1]-zy[1][70]中录入的最优价值都是0;

第二个药材重量69,价值为1,通过判断,当重量为70和69时,先不判断是不是最优,起码这个药材有被放进去的机会,然后重量为1-68时放不进去,所以直接继承上一个数组的值,因为上一个数组储存的值是截止上一个药材而言的最优解;当重量为70和69时,通过吗max函数比较:1.如果减去这个药材的重量,那么截止上一个药材对应的最优解加上这个药材的价值;2.当前重量对应的截止上一个函数的最优解;两值的大小,选取最大值存储;易得zy[2][70]=1,zy[2][69]=1;

第三个药材重量是1,价值是2,zy[3][70]中方案1对应的值是3,方案2是2,所以储存为三;

至此到答案,输出zy[3][70],结果为3;

题解如下:

#include <iostream>
#include <cmath>
using namespace std;
int zl[105], val[105];
int jzjl[105][1005];
int main()
{
    int t, m;
    cin >> t >> m;
    for (int i = 1; i <= 0; i++)
    {
           cin>> zl[i]>>val[i];
    }
    for (int i = 1; i <= m; i++) {
        for (int j = t; j >= t; j--)
        {
            if (j >= zl[i])
            {
                jzjl[i][j] = max(jzjl[i - 1][j - zl[i]] + val[i], jzjl[i - 1][j]);
                //cout<<"存进去" << i << " " << j << " " << jzjl[i][j] << endl;
            }
            else
            {
                jzjl[i][j] = jzjl[i - 1][j];
               //cout<<"存不进" << i << " " << j << " " << jzjl[i][j] << endl;
            }
        }
    }
   cout<< jzjl[m][t];
   system("pause");
    return 0;
}

B3637 最长上升子序列 

题目描述

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

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

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

输入格式

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

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

输出格式

一个整数表示答案。

输入输出样例

输入 #1

6
1 2 4 1 3 4

输出 #1

4

说明/提示

分别取出 11、22、33、44 即可。

题解:

从外围从第一个数开始遍历,内围从1遍历到外围数字-1,相当于给每个数字找低于自己的数字,然后叠高高,每次遍历的时候,作为外围的数字都可以选择自己对接上去后最长的队列,因为是一个一个排,所以一定是从小到大

像例题1,2,4,1,3,4排列起来顺序大概就是:

1 1 1 1 1 1 

   2 2    2 2

      4    3 3

               4

emmm,有点抽象,不过大概就是这样,从左向右就是顺序,

第一个数字1找不到人,最长队列就是自己,

第二个 数字2找到1,最长队列是1,2

第三个数字4找到1,2,最长队列是1,2,4

以此类推,不过我们不用记录队列是怎么排 的,只要记住他有几个数就够了,所以一个一维数组用来记录数据即可,最后找到最长的,输出,结束

#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
int a[5005];
int main()
{
	int n;
	cin >> n;
	vector<int>dp(n+1,1);
	dp[0] = 0;
	for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = 1; i <= n; i++)
	{
		dp[i] = 1;
				//cout << "i=" << i << endl;
		for (int j = 1; j <= i - 1; j++)
		{
				//cout << "j=" << j << endl;
			if (a[j] < a[i]) { 
				dp[i] = max<int>(dp[i], dp[j] + 1); 
				//cout<<"dp["<<i<<"]=" << dp[i] << endl;
			}
		}
	}
	sort(dp.begin() + 1, dp.end());
	cout << dp.back();
	system("pause");
	return 0;
}

P1115 最大子段和 

题目描述

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

输入格式

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

第二行有 nn 个整数,第 ii 个整数表示序列的第 ii 个数字 a_iai​。

输出格式

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

输入输出样例

输入 #1

7
2 -4 3 -1 2 -4 3

输出 #1

4

说明/提示

样例 1 解释

选取 [3, 5][3,5] 子段 \{3, -1, 2\}{3,−1,2},其和为 44。

数据规模与约定

  • 对于 40\%40% 的数据,保证 n \leq 2 \times 10^3n≤2×103。
  • 对于 100\%100% 的数据,保证 1 \leq n \leq 2 \times 10^51≤n≤2×105,-10^4 \leq a_i \leq 10^4−104≤ai​≤104。

题解:

这个题不难,思路还是比较清晰,不过开始想的太简单了。

先说下最开始时间超限的解法,直接两个for循环相套,外层从0到n-1遍历每个数,内层选择从外层记录的数开始最大是几,数组n用来记录内层选择的数据

明了一些来说就是:

m[e]:储存从这个数开始最大能加到几

int jishu:内层每加一个数,jishu就+=m[i]

然后不断判断m[e]和jishu谁大,谁大存谁,因为必须是连续的,所以只需要jishu一个变量就可以实现整个过程

当然,因为复杂程度是O(n2),所以五个案例三个报了超时,很悲伤。

痛定思痛优化了一下方法,既然jishu每次都要从外围数字一路加到最大,那是不是可以重复利用其中的这个过程呢。

然后就有了新思路:

反着来:

开始定义最优变量为int zy =0;因为最后一个数后面再没数了,加不了别人了

然后加一个判定,如果zy>0,那么统计数组h[e]=m[e]+zy;

                             否则h[e]=m[e];

很好理解,就是如果加上后面那个会变大那就加,变大不了那就拉倒,不管他了

然后zy更新为刚刚确定的h[e],此时zy就代表了从当前e开始向后加最大能加到多少,

然后外围往前推进,把每个数都遍历一遍,最后选出最大值输出,结束 

然后就成了,思路没太大变化,但是省去了内层的for循环,复杂度变成了O[n],就不会超时了 

#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
int main()
{
	int n;
	cin >> n;
	vector<int>m(n, 0);
	vector<int>h(n, 0);
	for (int e = 0; e < n; e++) {
		cin >> m[e];
	}
	int zy = 0;
	for (int e = n-1; e>=0; e--) {
		if (zy > 0) {
		h[e] = m[e] + zy;
		}
		else {
			h[e] = m[e];
		}
		zy = h[e];
	}
	sort(h.begin(), h.end());
	cout << h.back();
	system("pause");
	return 0;
}

 LCS

题意翻译

题目描述:

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

输入格式:

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

输出格式:

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

说明/提示:

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

输入输出样例

输入 #1

axyb
abyxb

输出 #1

axb

输入 #2

aa
xayaz

输出 #2

aa

输入 #3

a
z

输出 #3

 

输入 #4

abracadabra
avadakedavra

输出 #4

aaadara

题解:

先照dp模板,记录字符串a里面每个字符对应字符串b里面的每个字符能有多少个,算出怎么样会让公共子序列最长

假设字符串a是

abcde

字符串b是

eabcd 

那二维数组的赋值过程就是

a不等于e ,【1】【1】为0;

a等于a,【1】【2】为1;

a不等于b,【1】【3】为0;

a不等于c,【1】【4】为0;

a不等于d,【1】【5】为0;

下一步,开始字符串a的第二个字符b

b不等于e,【2】【1】为0;

b不等于a,但是因为【1】【2】是1,此时对于字符串“ab”来说可以继承a的结果,【2】【2】为1;

b等于b,【2】【3】为1;

b不等于c,【2】【4】为0;

b不等于d,【2】【5】为0;

以此类推,当不等于时就找二维数组左边和上边最大的一个录入 

然后再从后往前判断最长长度的字符串是怎么加上来的 ,最后输出

#include<iostream>
using namespace std;
int dp[3005][3005];
char a[3005], b[3005], c[3005];
int main()
{
	string a;
	string b;
	cin >> a >> b;
	a.insert(a.begin(), 0);
	b.insert(b.begin(), 0);
	int n = a.size()-1, m =b.size()-1;
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			if (a[i] == b[j])
			{
				dp[i][j] = dp[i - 1][j - 1] + 1;
			}
			else
			{
				dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
			}
		}
	}
	int i = n, j = m;
	while (dp[i][j] > 0)
	{
		if (a[i] == b[j])
		{
			c[dp[i][j]] = a[i];
			i--;
			j--;
		}
		else
		{
			if (dp[i][j] == dp[i - 1][j]) i--;
			else j--;
		}
	}
	printf("%s", c + 1);
	system("pause");
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

唏嘘南溪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值