您的位置:首页 > 其它

动态规划专题之最长上升子序列

2017-08-09 11:30 162 查看
专题二:最长上升子序列
/*
Name: 动态规划专题之最长上升子序列
Author: 巧若拙
Description: 1759_最长上升子序列
描述:一个数的序列bi,当b1 < b2 < ... < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, ..., aN),我们可以得到一些上升的子序列(ai1, ai2, ..., aiK),这里1 <= i1 < i2 < ... < iK <= N。
比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8).
你的任务,就是对于给定的序列,求出最长上升子序列的长度。
输入
输入的第一行是序列的长度N (1 <= N <= 1000)。第二行给出序列中的N个整数,这些整数的取值范围都在0到10000。
输出:最长上升子序列的长度
样例输入:
7
1 7 3 5 9 4 8
样例输出
4
*/
#include<iostream>
using namespace std;

const int MAX = 1001;
int A[MAX];
int S1[MAX]; //记录到元素i为止的最长上升子序列的长度
int S2[MAX]; //记录到元素i为止的最长上升子序列的长度
int S3[MAX+1]; //记录最长上升子序列,下标从1开始

int DP_1(int n); //逆序处理,返回最长上升子序列第一个元素的下标
int DP_2(int n); //顺序处理
int DP_3(int n); //顺序处理,二分插入
int Pos(int low, int high, int x);//二分查找,返回第一个比x大的元素下标

int main()
{
int n;
cin >> n;
for (int i=0; i<n; i++)
{
cin >> A[i];
}

int pos = DP_1(n);//逆序处理
cout << S1[pos] << endl;
cout << DP_2(n) << endl;//顺序处理
cout << DP_3(n) << endl;//顺序处理,二分插入

return 0;
}

算法1:逆序处理,需要用到全局变量A[MAX],另有S1[MAX]初始化为0。
int DP_1(int n) //逆序搜索,返回最长上升子序列第一个元素的下标
{
int pos = n - 1;

for (int i=n-1; i>=0; i--)
{
int len = 0;
for (int j=i+1; j<n; j++)
{
if (A[j] > A[i] && S1[j] > len)
len = S1[j];
}
S1[i] = len + 1;

if (S1[i] > S1[pos])
pos = i;
}

return pos;
}

问题1:根据样例输入:1 7 3 5 9 4 8,写出对应数组S1[]的值。
问题2:修改DP_1(),使其返回最长上升子序列的长度。
问题3:DP_1()返回的不是最长上升子序列的长度,而是其第一个元素的下标,目的是为了输出该最长上升子序列,请在DP_1()的基础上,编写一段代码,输出该最长上升子序列。

参考答案:
问题1:数组S1[]的值为:4 2 3 2 1 2 1。
问题2:代码如下:
int DP_1(int n) //逆序搜索,返回最长上升子序列长度
{
int maxLen = 0; //记录最长上升子序列的长度

for (int i=n-1; i>=0; i--)
{
for (int j=i+1; j<n; j++)//在A[i]后面的元素中查找最大的S1[j]
{
if (A[j] > A[i] && S1[j] > S1[i])
S1[i] = S1[j];
}
S1[i]++;
if (maxLen < S1[i])
maxLen = S1[i];
}

return maxLen;
}
问题3:代码如下:
int pos = DP_1(n);
int len = S1[pos]; //子序列的长度
for (int i=pos; len>0; i++) //总共输出len个元素
{
if (S1[i] == len)
{
cout << A[i] << " ";
len--;
}
}
cout << endl;

算法2:顺序处理,需要用到全局变量A[MAX],另有S2[MAX]初始化为0。
int DP_2(int n) //顺序搜索
{
int maxLen = 0;

for (int i=0; i<n; i++)
{
for (int j=i-1; j>=0; j--) //语句1
{
if (A[i] > A[j] && S2[j] > S2[i])
S2[i] = S2[j];
}
S2[i]++;
if (maxLen < S2[i])
maxLen = S2[i];
}

return maxLen;
}

问题1:根据样例输入:1 7 3 5 9 4 8,写出对应数组S2[]的值。
问题2:语句1能否改为:for (int j=0; j<i; j++) ?哪种写法效率更高?
问题3:语句1所在循环体能否改写为:
for (int j=i-1; j>=0; j--)
{
if (A[i] > A[j])
{
S2[i] = S2[j] + 1;
break;
}
}
如果不行,请给出一个样例输入进行说明。
问题4:若把题目改为:求出最长不下降子序列的长度。该如何修改DP_2的代码?

参考答案:
问题1:数组S2[]的值为:1 2 2 3 4 3 4。
问题2:可以。因为语句1所在循环体的作用是在A[0...i-1]中,找出一个比A[i]小且最长的上升子序列,故顺序查找和逆序查找均可。但是对于上升子序列来说,S2[j]的值是递增的,逆序查找能更快地找到最大的S2[j],故语句1的写法效率更高。
问题3:不行。因为可能存在多个上升子序列,不能只找到一个就结束搜索,一定要完全搜索。样例输入:1 3 4 2 5 。正确答案为4,如果采用问题3的代码,则得到错误答案为3。
问题4:把语句if (A[i] > A[j] && S2[j] > S2[i])改为if (A[i] >= A[j] && S2[j] > S2[i])即可。

算法3:顺序处理,二分插入,需要用到全局变量A[MAX],另有S3[MAX]初始化为0。
int DP_3(int n) //顺序搜索,二分插入
{
int m = 0; //记录最长不下降子序列的长度

S3[++m] = A[0];
for (int i=1; i<n; i++)
{
if (A[i] > S3[m])
{
S3[++m] = A[i];
}
else
{
S3[Pos(1, m-1, A[i])] = A[i];  //语句1
}
}

return m;
}

int Pos(int low, int high, int x)//二分查找,返回第一个比x大的元素下标
{
int mid;

while (low <= high)
{
mid = (low + high)/2;
if (S3[mid] > x)
{
high =   //语句2
}
else
{
low =   //语句3
}
}

return   //语句4
}

问题1:根据样例输入:1 7 3 5 9 4 8,写出对应数组S3[]的值。
问题2:语句1能否改为:S3[Pos(0, m, A[i])] = A[i];?为什么?
问题3:将语句2,语句3和语句4补充完整。

参考答案:
问题1:数组S3[]的值为:1 3 4 8。
问题2:不能。因为数组S3的下标从1开始,故不能写作S3[Pos(0, m, A[i])] = A[i];虽然可以写作S3[Pos(1, m, A[i])] = A[i];但是因为我们已经知道A[i] > S3[m],故在S[1...m-1]中二分查找第一个比x大的元素下标即可,因此语句1的写法更好。
问题3:语句2:high = mid - 1; 语句3:low = mid + 1; 语句4:return low;

课后练习:
练习1:1044_拦截导弹  1999年NOIP全国联赛提高组
题目描述:某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。
某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入描述 Input Description
输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数)
输出描述 Output Description
输出这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
样例输入 Sample Input
389 207 155 300 299 170 158 65
样例输出 Sample Output
6(最多拦截导弹数)
2(要拦截所有导弹最少要配备的系统数)

数据范围及提示 Data Size & Hint
导弹的高度<=30000,导弹个数<=20

练习2:3532_最大上升子序列和
描述:一个数的序列bi,当b1 < b2 < ... < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, ...,aN),我们可以得到一些上升的子序列(ai1, ai2, ..., aiK),这里1 <= 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 (1 <= N <= 1000)。第二行给出序列中的N个整数,这些整数的取值范围都在0到10000(可能重复)。
输出
最大上升子序列和
样例输入
7
1 7 3 5 9 4 8
样例输出
18

练习3:4977_怪盗基德的滑翔翼
描述:怪盗基德是一个充满传奇色彩的怪盗,专门以珠宝为目标的超级盗窃犯。而他最为突出的地方,就是他每次都能逃脱中村警部的重重围堵,而这也很大程度上是多亏了他随身携带的便于操作的滑翔翼。
有一天,怪盗基德像往常一样偷走了一颗珍贵的钻石,不料却被柯南小朋友识破了伪装,而他的滑翔翼的动力装置也被柯南踢出的足球破坏了。不得已,怪盗基德只能操作受损的滑翔翼逃脱。
假设城市中一共有N幢建筑排成一条线,每幢建筑的高度各不相同。初始时,怪盗基德可以在任何一幢建筑的顶端。他可以选择一个方向逃跑,但是不能中途改变方向(因为中森警部会在后面追击)。因为滑翔翼动力装置受损,他只能往下滑行(即:只能从较高的建筑滑翔到较低的建筑)。他希望尽可能多地经过不同建筑的顶部,这样可以减缓下降时的冲击力,减少受伤的可能性。
请问,他最多可以经过多少幢不同建筑的顶部(包含初始时的建筑)?
输入
输入数据第一行是一个整数K(K < 100),代表有K组测试数据。
每组测试数据包含两行:第一行是一个整数N(N < 100),代表有N幢建筑。
第二行包含N个不同的整数,每一个对应一幢建筑的高度h(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

练习4:电路布线
【问题描述】在一块电路板的上、下两端分别有n个接线柱。根据电路设计,要求用导线(i,π(i))将上端接线柱i与下端接线柱π(i)相连。
其中,π(i),1<=i<=n是{1,2,…,n}的一个排列。导线(i,π(i))称为该电路板上的第i条连线。对于任何1<=i π(j)。
在制作电路板时,要求将这n条连线分布到若干绝缘层上。在同一层上的连线不相交。
你的任务是要确定将哪些连线安排在第一层上,使得该层上有尽可能多的连线。
换句话说,就是确定导线集Nets={ i,π(i),1<=i<=n}的最大不相交子集。
【输入形式】
输入文件第一行为整数n;第二行为用一个空格隔开的n个整数,表示π(i)。
【输出形式】
输出文件第一行为最多的连线数m,第2行到第m+1行输出这m条连线(i,π(i))。
【输入样例】
10
1 8
2 7
3 4
4 2
5 5
6 1
7 9
8 3
9 10
10 6
【输出样例】
4

练习5:友好城市
【问题描述】 Palmia国有一条横贯东西的大河,河有笔直的南北两岸,岸上各有位置各不相同的N个城市。北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同。每对友好城市都向政府申请在河上开辟一条直线航道连接两个城市,但是由于河上雾太大,政府决定避免任意两条航道交叉,以避免事故。
编程帮助政府做出一些批准和拒绝申请的决定,使得在保证任意两条航线不相交的情况下,被批准的申请尽量多。
【输入格式】
  第1行,一个整数N(1<=N<=5000),表示城市数。
  第2行到第n+1行,每行两个整数,中间用1个空格隔开,分别表示南岸和北岸的一对友好城市的坐标。(0<=xi<=10000)
【输出格式】
  仅一行,输出一个整数,政府所能批准的最多申请数。
【输入样例】
  7
  22 4
  2 6
  10 3
  15 12
  9 8
  17 17
  4 2
【输出样例】
4

练习6:1058_合唱队形  2004年NOIP全国联赛提高组
描述:N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK,  则他们的身高满足T1<...<Ti>Ti+1>…>TK(1<=i<=K)。
你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
输入描述 Input Description
输入文件chorus.in的第一行是一个整数N(2<=N<=100),表示同学的总数。第一行有n个整数,用空格分隔,
第i个整数Ti(130<=Ti<=230)是第i位同学的身高(厘米)。
输出描述 Output Description
输出文件chorus.out包括一行,这一行只包含一个整数,就是最少需要几位同学出列。
样例输入 Sample Input
8
186 186 150 200 160 130 197 220
样例输出 Sample Output
4

练习7:1996_登山
描述:五一到了,PKU-ACM队组织大家去登山观光,队员们发现山上一个有N个景点,并且决定按照顺序来浏览这些景点,即每次所浏览景点的编号都要大于前一个浏览景点的编号。同时队员们还有另一个登山习惯,就是不连续浏览海拔相同的两个景点,并且一旦开始下山,就不再向上走了。队员们希望在满足上面条件的同时,尽可能多的浏览景点,你能帮他们找出最多可能浏览的景点数么?
输入
Line 1: N (2 <= N <= 1000) 景点数
Line 2: N个整数,每个景点的海拔
输出
最多能浏览的景点数
样例输入
8
186 186 150 200 160 130 197 220
样例输出
4
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: