最长上升子序列
题目来源: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] 可以分两种情况来讨论:
- 要是在 a[i] 之前存在元素 a[j] 使得 a [ j ] < = a [ i ] a[j]<=a[i] a[j]<=a[i]&& d p [ j ] + 1 > d p [ i ] dp[j] + 1 > 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。
- 如果 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 > d [ i ] d[j] + 1>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(有两个中点)