动态规划——最长上升子序列模型

 

最长上升子序列

给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。

输入格式

第一行包含整数 N。

第二行包含 N 个整数,表示完整序列。

输出格式

输出一个整数,表示最大长度。

数据范围

1≤N≤1000
−109≤数列中的数≤109

按照往常方法分析状态转移方程和集合表示。

给如一般代码如下:

普通版

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
typedef long long LL;
typedef pair<int,int>PII;
const int N = 1010;
int f[N],g[N];
int n;
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int ans = 0;
	cin >> n;
	for (int i = 1; i <= n; i++)
		cin >> g[i];
	for (int i = 1; i <= n; i++)
	{
		f[i] = 1;
		for (int j = 1; j <= i - 1; j++)
		{
			if (g[j] < g[i])
			{
				f[i] = max(f[i], f[j] + 1);
			}
		}
	}
	for (int i = 1; i <= n; i++)
	{
		ans= max(ans, f[i]);
	}
	cout << ans;
	return 0;

}

优化版:

利用单调栈进行优化

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
typedef long long LL;
typedef pair<int,int>PII;
const int N = 1e5 + 10;
int a[N];
int n;
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin >> n;
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	vector<int>stk;
	stk.push_back(a[1]);
	for (int i = 1; i <= n; i++)
	{
		if (a[i] > stk.back())
		{
			stk.push_back(a[i]);
		}
		else
		{
			*lower_bound(stk.begin(), stk.end(), a[i]) = a[i];
		}
	}
	cout << stk.size();
	return 0;
}

  

例题1:怪盗基德的滑翔翼

怪盗基德是一个充满传奇色彩的怪盗,专门以珠宝为目标的超级盗窃犯。

而他最为突出的地方,就是他每次都能逃脱中村警部的重重围堵,而这也很大程度上是多亏了他随身携带的便于操作的滑翔翼。

有一天,怪盗基德像往常一样偷走了一颗珍贵的钻石,不料却被柯南小朋友识破了伪装,而他的滑翔翼的动力装置也被柯南踢出的足球破坏了。

不得已,怪盗基德只能操作受损的滑翔翼逃脱。

假设城市中一共有N幢建筑排成一条线,每幢建筑的高度各不相同。

初始时,怪盗基德可以在任何一幢建筑的顶端。

他可以选择一个方向逃跑,但是不能中途改变方向(因为中森警部会在后面追击)。

因为滑翔翼动力装置受损,他只能往下滑行(即:只能从较高的建筑滑翔到较低的建筑)。

他希望尽可能多地经过不同建筑的顶部,这样可以减缓下降时的冲击力,减少受伤的可能性。

请问,他最多可以经过多少幢不同建筑的顶部(包含初始时的建筑)?

输入格式

输入数据第一行是一个整数K,代表有K组测试数据。

每组测试数据包含两行:第一行是一个整数N,代表有N幢建筑。第二行包含N个不同的整数,每一个对应一幢建筑的高度h,按照建筑的排列顺序给出。

输出格式

对于每一组测试数据,输出一行,包含一个整数,代表怪盗基德最多可以经过的建筑数量。

数据范围

1≤K≤100
1≤N≤100
0<h<10000

输入样例:

3
8
300 207 155 299 298 170 158 65
8
65 158 170 298 299 155 207 300
10
2 1 3 4 5 6 7 8 9 10

输出样例:

6
6
9

 例1分析:考虑到我们不能确定基德飞的方向,所以需要从前往后求一遍lcs,和从后往前求一遍lcs,基本思路和模板相同。代码如下

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
typedef long long LL;
typedef pair<int,int>PII;
const int N = 110;
int k, n;
int g[N], f[N];
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin >> k;
	while (k--)
	{
		cin >> n;
		for (int i = 1; i <= n; i++)
		{
			cin >> g[i];
		}
		int res = 0, ans = 0;
		for (int i = 1; i <= n; i++)
		{
			f[i] = 1;
			for (int j = 1; j <= i - 1; j++)
			{
				if (g[j] < g[i])
				{
					f[i] = max(f[i], f[j] + 1);
				}
			}
		}
		for (int i = 1; i <= n; i++)
		{
			res = max(res, f[i]);
		}
		memset(f, 0, sizeof f);
		for (int i = n; i >= 1; i--)
		{
			f[i] = 1;
			for (int j = n; j >= i + 1; j--)
			{
				if (g[j] < g[i])
				{
					f[i] = max(f[i], f[j] + 1);
				}
			}
		}
		for (int i = 1; i <= n; i++)
		{
			ans = max(ans, f[i]);
		}
		cout << max(ans, res) << endl;


	}
	return 0;
}

例题2:登山

五一到了,ACM队组织大家去登山观光,队员们发现山上一共有N个景点,并且决定按照顺序来浏览这些景点,即每次所浏览景点的编号都要大于前一个浏览景点的编号。

同时队员们还有另一个登山习惯,就是不连续浏览海拔相同的两个景点,并且一旦开始下山,就不再向上走了。

队员们希望在满足上面条件的同时,尽可能多的浏览景点,你能帮他们找出最多可能浏览的景点数么?

输入格式

第一行包含整数N,表示景点数量。

第二行包含N个整数,表示每个景点的海拔。

输出格式

输出一个整数,表示最多能浏览的景点数。

数据范围

2≤N≤1000

输入样例:

8
186 186 150 200 160 130 197 220

输出样例:

4

 例2分析:

   考虑到一旦下山就不能再上山,那么为了保证浏览最多的景点,那么方案一定是一段先递增后递减的山峰,为了保证总体最大,可以开一个数组存放从前往后遍历的值,再从后往求一段lcs即可,同时取max即可。给出代码如下,特别需要注意的是每个点再计算自己时都被用到了两次所以需要减去1(因为这debug了好一会);

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
typedef long long LL;
typedef pair<int,int>PII;
const int N = 1010;
int f[N], g[N],a[N];
int n;
bool flag;
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin >> n;
	int res = 0;
	for (int i = 1; i <= n; i++)
	{
		cin >> g[i];
	}
	for (int i = 1; i <= n; i++)
	{
		f[i] = 1;
		for (int j = 1; j <= i - 1; j++)
		{
			if (g[j] < g[i])
			{
				f[i] = max(f[i], f[j] + 1);
			}
		}
	}
	for (int i = 1; i <= n; i++)
	{
		a[i] += f[i];
	}
	memset(f, 0, sizeof f);
	for (int i = n; i >= 1; i--)
	{
		f[i] = 1;
		for (int j = n; j >= i + 1; j--)
		{
			if (g[j] < g[i])
			{
				f[i] = max(f[i], f[j] + 1);
			}
		}
	}
	for (int i = 1; i <= n; i++)
	{
		a[i] += f[i];
		res = max(res, a[i]);
	}
	//这里需要减一的原因是从前往后和从后往前数,当前点被计算了两次。
	cout << res-1;

}

例题3:合唱队形

N 位同学站成一排,音乐老师要请其中的 (N−K)(N−K) 位同学出列,使得剩下的 KK 位同学排成合唱队形。     

合唱队形是指这样的一种队形:设 KK 位同学从左到右依次编号为 1,2…,K他们的身高分别为 T1,T2,…,TK  则他们的身高满足 T1<…<Ti>Ti+1>…>TK(1≤i≤K)>。     

你的任务是,已知所有 N 位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

输入格式

输入的第一行是一个整数 N,表示同学的总数。

第二行有 N 个整数,用空格分隔,第 i 个整数 Ti 是第 i 位同学的身高(厘米)。

输出格式

输出包括一行,这一行只包含一个整数,就是最少需要几位同学出列。

数据范围

2≤N≤100
130≤Ti≤230

输入样例:

8
186 186 150 200 160 130 197 220

输出样例:4

正在上传…重新上传取消

 例3分析:

和前面的登山问题类似,这里仍然可以用从前往后和从后往前的总最大值求组,用n减去(res-1)即可,给出ac码如下。

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
typedef long long LL;
typedef pair<int,int>PII;
const int N = 110;
int f[N], g[N],a[N];
int n;
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin >> n;
	int res = 0;
	for (int i = 1; i <= n; i++)
	{
		cin >> g[i];
	}
	for (int i = 1; i <= n; i++)
	{
		f[i] = 1;
		for (int j = 1; j <= i - 1; j++)
		{
			if (g[j] < g[i])
			{
				f[i] = max(f[i], f[j] + 1);
			}
		}
	}
	for (int i = 1; i <= n; i++)
	{
		a[i] += f[i];
	}
	memset(f, 0, sizeof f);
	for (int i = n; i >= 1; i--)
	{
		f[i] = 1;
		for (int j = n; j >= i + 1; j--)
		{
			if (g[j] < g[i])
			{
				f[i] = max(f[i], f[j] + 1);
			}
		}
	}
	for (int i = 1; i <= n; i++)
	{
		a[i] += f[i];
		res = max(res, a[i]);

	}
	cout << n - res + 1;
	return 0;

}

例4:最大上升子序列和

一个数的序列 bi,当 b1<b2<…<bSb1<b2<…<bS 的时候,我们称这个序列是上升的。

对于给定的一个序列(a1,a2,…,aNa1,a2,…,aN),我们可以得到一些上升的子序列(ai1,ai2,…,aiKai1,ai2,…,aiK),这里1≤i1<i2<…<iK≤N1≤i1<i2<…<iK≤N。

比如,对于序列(1,7,3,5,9,4,8),有它的一些上升子序列,如(1,7),(3,4,8)等等。

这些子序列中和最大为18,为子序列(1,3,5,9)的和。

你的任务,就是对于给定的序列,求出最大上升子序列和。

注意,最长的上升子序列的和不一定是最大的,比如序列(100,1,2,3)的最大上升子序列和为100,而最长上升子序列为(1,2,3)。

输入格式

输入的第一行是序列的长度N。

第二行给出序列中的N个整数,这些整数的取值范围都在0到10000(可能重复)。

输出格式

输出一个整数,表示最大上升子序列和。

数据范围

1≤N≤1000

输入样例:

7
1 7 3 5 9 4 8

输出样例:

18

 例4分析:本题可以和最长上升子序列相联系,考虑到求取最长上升子序列时我们的转移方程f[i]取的是1,那么这里的权值改成题目中所给即可,不要忘记转移方程是也要修改,代码如下。

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
typedef long long LL;
typedef pair<int,int>PII;
const int N = 1010;
int n;
int g[N], f[N];
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int res = 0;
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> g[i];
	for (int i = 1; i <= n; i++)
	{
		f[i] = g[i];
		for (int j = 1; j <= i - 1; j++)
		{
			if (g[j] < g[i])
			{
				f[i] = max(f[i], f[j] + g[i]);
			}
		}
	}
	for (int i = 1; i <= n; i++)
	{
		res = max(res, f[i]);
	}
	cout << res;
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值