最长上升子序列(Longest Increasing Sequence)[LIS]

博客围绕最长上升子序列、最长不上升子序列、合唱队形和士兵队形问题展开。介绍了各问题的题目描述、输入输出要求及样例,还给出了思路,如用动态规划求解最长上升和不上升子序列,合唱队形和士兵队形问题则正反运用LIS求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最长上升子序列

题目来源:codeup链接

题目描述
一个数列ai如果满足条件a1 < a2 < … < aN,那么它是一个有序的上升数列。我们取数列(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个自然数ai,0 <= ai <= 10000,两个数之间用空格隔开。

输出
输出只有一行,包含一个整数,表示最长上升子序列的长度。

样例输入
7
1 7 3 5 9 4 8
样例输出
4

思路:
dp[i] 表示以 a[i] 结尾的最长不下降子序列长度。(以a[i]结尾是强制的要求)
此时 dp[i] 可以分两种情况来讨论:

  1. 要是在 a[i] 之前存在元素 a[j] 使得 a [ j ] &lt; = a [ i ] a[j]&lt;=a[i] a[j]<=a[i]&& d p [ j ] + 1 &gt; d p [ i ] dp[j] + 1 &gt; dp[i] dp[j]+1>dp[i],那么就可以把a[i]放在a[j]后面形成一条更长的不下降子序列。此时 d p [ i ] = d p [ j ] + 1 dp[i] = dp[j] + 1 dp[i]=dp[j]+1
  2. 如果 a[i] 之前的元素都比 a[i] 大,那么 a[i] 只好自己形成一条LIS。

综上,最后以 a[i] 结尾的LIS长度就是1和2中的最大值。

#include <iostream>
using namespace std;
const int maxn = 1001;
int a[maxn];
int dp[maxn];
int n;
int search(int arr[])
{
    int res = 1;
    for (int i = 1; i < n; i++)
    {
        for (int j = 0; j < i; j++)
        {
            if (a[i] > a[j] && dp[j] + 1 > dp[i]) //不要写成a[i]>=a[j],因为是最长上升子序列,不是最长不下降子序列,codeup不会判错,百练会WA...
            {
                dp[i] = dp[j] + 1;
            }
        }
        if (res < dp[i])
        {
            res = dp[i];
        }
    }
    return res;
}
int main()
{
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        cin >> a[i];
    }
    for (int i = 0; i < n; i++)
    {
        dp[i] = 1;
    }
    cout << search(a) << endl;
    return 0;
}

最长不上升子序列

题目来源:codeup 拦截导弹

题目描述
某国为了防御敌国的导弹袭击,开发出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭,并观测到导弹依次飞来的高度,请计算这套系统最多能拦截多少导弹。拦截来袭导弹时,必须按来袭导弹袭击的时间顺序,不允许先拦截后面的导弹,再拦截前面的导弹。

输入
每组输入有两行,第一行,输入雷达捕捉到的敌国导弹的数量k(k<=25),第二行,输入k个正整数,表示k枚导弹的高度,按来袭导弹的袭击时间顺序给出,以空格分隔。

输出
每组输出只有一行,包含一个整数,表示最多能拦截多少枚导弹。

样例输入
4
9 6 7 8
7
4 5 6 7 13 42 3
5
6 5 4 3 5
0
样例输出
2
2
4

思路

  • d p [ i ] dp[i] dp[i]表示以 a [ i ] a[i] a[i]结尾的最长不上升子序列的个数。
    • 若存在 a [ j ] ≤ a [ i ] a[j] \leq a[i] a[j]a[i] && d [ j ] + 1 &gt; d [ i ] d[j] + 1&gt;d[i] d[j]+1>d[i],那么就可以把a[i]放在a[j]后面形成一条更长的不上升子序列。此时 d p [ i ] = d p [ j ] + 1 dp[i] = dp[j] + 1 dp[i]=dp[j]+1
    • 如果 a [ i ] a[i] a[i]之前的元素都比 a [ i ] a[i] a[i]小,那么 a [ i ] a[i] a[i]只好自己形成一条不上升子序列。-
  • 综上: d p [ i ] dp[i] dp[i]就是二者中较大的那个。
#include <iostream>
using namespace std;
const int maxn = 30;
int a[maxn];
int dp[maxn];
int n;
int search(int arr[])
{
	int res = 1;
	for (int i = 1; i < n; i++)
	{
		for (int j = 0; j < i; j++)
		{
			if (a[i] <= a[j] && dp[j] + 1 > dp[i])
			{
				dp[i] = dp[j] + 1;
			}
		}
		if (res < dp[i])
		{
			res = dp[i];
		}
	}
	return res;
}
int main()
{
	while (cin >> n && n)
	{
		for (int i = 0; i < n; i++)
		{
			cin >> a[i];
		}
		for (int i = 0; i < n; i++)
		{
			dp[i] = 1;
		}
		cout << search(a) << endl;
	}
	return 0;
}

合唱队形

题目描述
N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学不交换位置就能排成合唱队形。
合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1, 2, …, K,他们的身高分别为T1, T2, …, TK,
则他们的身高满足T1 < T2 < … < Ti , Ti > Ti+1 > … > TK (1 <= i <= K)。
你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

输入
输入的第一行是一个整数N(2 <= N <= 100),表示同学的总数。
第一行有n个整数,用空格分隔,第i个整数Ti(130 <= Ti <= 230)是第i位同学的身高(厘米)。

输出
可能包括多组测试数据,对于每组数据,
输出包括一行,这一行只包含一个整数,就是最少需要几位同学出列。

样例输入
3
174 208 219
6
145 206 193 171 187 167
0
样例输出
0
1

思路

  • 正反两次运用LIS。
  • 假设第 i i i个同学为最高点,分别求出从左边到达 i i i的最长上升子序列(正向LIS) d p 1 [ i ] dp1[i] dp1[i]和从右边到达 i i i的最长上升子序列(反向LIS) d p 2 [ i ] dp2[i] dp2[i]。由于最高点 i i i同时包括在了 d p 1 [ i ] dp1[i] dp1[i] d p 2 [ i ] dp2[i] dp2[i]之中,因此实际的合唱队形的长度为 d p 1 [ i ] + d p 2 [ i ] + 1 dp1[i]+dp2[i]+1 dp1[i]+dp2[i]+1。而我们求得的最后结果就是 i i i 1 1 1 n n n中,使得 d p 1 [ i ] + d p 2 i ] + 1 dp1[i]+dp2i]+1 dp1[i]+dp2i]+1最大的情况。
#include <iostream>
using namespace std;
const int maxn = 102;
int a[maxn];
int dp1[maxn];
int dp2[maxn];
int n;
int search(int arr[])
{
	for (int i = 0; i <= n; i++)
	{
		dp1[i] = 1;
		dp2[i] = 1;
	}
	for (int i = 2; i <= n; i++)
	{
		for (int j = 1; j < i; j++)
		{
			if (a[i] > a[j] && dp1[j] + 1 > dp1[i])
			{
				dp1[i] = dp1[j] + 1;
			}
		}
	}
	for (int i = n; i >= 1; i--)
	{
		for (int j = i+1; j <=n; j++)
		{
			if (a[i] > a[j] && dp2[j] + 1 > dp2[i])
			{
				dp2[i] = dp2[j] + 1;
			}
		}
	}
	int res = -1;
	for (int i = 1; i <= n; i++)
	{
		dp1[i] = dp1[i] + dp2[i];
		if (dp1[i] > res)
		{
			res = dp1[i];
		}
	}
	return n - res + 1;
}
int main()
{
	while (cin >> n && n)
	{
		for (int i = 1; i <= n; i++)
		{
			cin >> a[i];
		}
		cout << search(a) << endl;
	}
	return 0;
}

士兵队形

题目来源:poj 1836

正反两次运用LIS,不过这题与合唱队形不同的是可以有两个中点。第一个中点的左边全部往左看。第二个中点的右边全部往右看。

#include <iostream>
using namespace std;
const int maxn = 1010;
double a[maxn];
int dp1[maxn];
int dp2[maxn];
int n;
int search(double arr[])
{
    for (int i = 0; i <= n; i++)
    {
        dp1[i] = 1;
        dp2[i] = 1;
    }
    for (int i = 2; i <= n; i++)
    {
        for (int j = 1; j < i; j++)
        {
            if (a[i] > a[j] && dp1[j] + 1 > dp1[i])
            {
                dp1[i] = dp1[j] + 1;
            }
        }
    }
    for (int i = n; i >= 1; i--)
    {
        for (int j = i + 1; j <= n; j++)
        {
            if (a[i] > a[j] && dp2[j] + 1 > dp2[i])
            {
                dp2[i] = dp2[j] + 1;
            }
        }
    }
    int res = -1;
    for (int i = 1; i <= n; i++) //中点只有一个的时候
    {
        res = max(dp1[i] + dp2[i] - 1, res);
    }

    for (int i = 1; i <= n - 1; i++) //中点有两个的时候
    {
        for (int j = i + 1; j <= n; j++)
        {
            res = max(dp1[i] + dp2[j], res);
        }
    }
    return n - res;
}
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    cout << search(a) << endl;
    return 0;
}

此题需要注意的样例数据:
8
3 4 5 1 2 5 4 3
答案是:2(有两个中点)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值