更好的阅读体验请前往:Paxton的小破站
一、最长上升/下降子序列
分为最长不上升子序列,最长上升子序列,最长不下降子序列,最长下降子序列四种。
以最长上升子序列为例
例如5 6 7 1 2 8 这个序列,他的最长上升子序列为5 6 7 8
最直接能想到的算法就是逐个数字进行双层循环向后查找比自己大的数
显然这样的算法是非常容易超时的
我们可以定义一个d数组来存储这个上升子序列,使用len表示这个子序列的长度
使用一层循环遍历一次原数组,将每个数与子序列中的最后一个数字进行比较
若它大于子序列中的末尾数字,则将这个数字加入子序列中,因为它满足了上升子序列的要求,len++
若他小于等于子序列中的末尾数字则使用二分查找寻找子序列第一个大于等于的数字并替换。
为什么要这么替换呢?
假设我们的原数列是非常长的一个数列,可以理解为这个数字比子序列中的数字更有潜力去接收更多的上升数字。
例如5 6 7 1 2 8这个数列,假设他往后还有很多数字,那么按照上面的做法,遍历到数字1的时候,子序列此时为5 6 7,数字1小于数字7,则在子序列中找到大于或等于1的第一个数字,也就是5,将他换掉,子序列变成1 6 7
那么后面如果出现了2 3 4 5…这些数字则统统可以将他们替换子序列或加入子序列,这就是所谓的更有潜力
如果后面没有数字了,说明他虽然有潜力接收更多的数字但是没有数字给他接收,len没有进行操作,所以不会影响子序列长度,如果后面还有可以进行操作的数字,则继续按上面的操作进行替换或增加。
5 6 7 1 2 8数列的子序列的变化过程如下
5 //5>0 ,len++
5 6 //6>5 ,len++
5 6 7 //7>6 ,len++
1 6 7 //1<7 ,将5替换,len不变
1 2 7 //2<7 ,将6替换,len不变
1 2 7 8 //8>7 ,len++
最后结果便是4
其他的三种子序列道理相同,总结如下
1、最长不下降 若大于等于末尾则加入序列 否则 寻找序列第一个大于的数字并替换
2、最长不上升 若小于等于末尾则加入序列 否则 寻找序列第一个小于的数字并替换
3、最长上升 若大于末尾则加入序列 否则 寻找序列第一个大于等于的数字并替换
4、最长下降 若小于末尾则加入序列 否则 寻找序列第一个小于等于的数字并替换
二、例题一(拦截导弹)
题目链接:https://vjudge.net/contest/477404#problem/A
(1)题干
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
(2)输入格式
1行,若干个整数(个数≤100000)
(3)输出格式
输出 n 行,每行 n 个正整数。
第i行第j列的正整数表示(i,j)这个格子被多少个地毯覆盖。
(4)样例
sample Input
389 207 155 300 299 170 158 65
sample output
6
2
例题一题解
1、分析
根据题意,每次发射的高度不能大于上一次的高度,可以知道,本题第一问即求一个最长不上升子序列
第二问,根据样例和自建样例可以推导得知,题目要求一个最长上升子序列
2、代码
#include <algorithm>
#include <iostream>
using namespace std;
int a[100005], d1[100005], d2[100005], n;
int main()
{
while (cin >> a[++n]){}//循环输入
n--;
int len1 = 1, len2 = 1;
d1[1] = a[1]; //最长不上升子序列
d2[1] = a[1]; //最长上升子序列
for (int i = 2; i <= n; ++i) //从第二个数开始枚举每个数
{
if (d1[len1] >= a[i]) //如果下一个数字不大于序列中末尾的数字
{
d1[++len1] = a[i]; //将下一个数字加入数列,同时可以拦截的数量计数+1
}
else
{
int p1 = upper_bound(d1 + 1, d1 + 1 + len1, a[i], greater<int>()) - d1;
d1[p1] = a[i]; //将其替换
}
//-----------------------------------------------------
if (d2[len2] < a[i]) //如果下一个数字大于序列中末尾的数字
{
d2[++len2] = a[i]; //将下一个数字加入数列
}
else
{
int p2 = lower_bound(d2 + 1, d2 + 1 + len2, a[i]) - d2;
d2[p2] = a[i]; //将其替换
}
}
cout << len1 << endl
<< len2;
return 0;
}
三、例题二(最长公共子序列)
题目链接:https://vjudge.net/contest/477404#problem/B
(1)题干
给出1∼n 的两个排列 P1和 P2 ,求它们的最长公共子序列。
(2)输入格式
第一行是一个数 n(1≤n≤10^5)。
接下来两行,每行为 n 个数,为自然数 1∼n 的一个排列。
(3)输出格式
一个数,即最长公共子序列的长度。
(4)样例
sample Input
5
3 2 1 4 5
1 2 3 4 5
sample output
3
例题二题解
1、分析
由于这俩个数列都是1-n的全排列的其中两种情况,说明这两个数列中的数字都是完全一致的只是顺序不同
根据洛谷题解的分析
我们可以将第二个数列作为数字的大小关系,来查找第一个数列的最长上升子序列,得出的长度就是答案
即数字在第二个数列的位置越靠后,这个数字就越“大”
2、代码
#include <algorithm>
#include <iostream>
using namespace std;
int n;
int a[100005], map[100005], d1[100005];
int main()
{
cin >> n;
for (int i = 1; i <= n; ++i)
{
cin >> a[i];
}
for (int i = 1; i <= n; ++i)
{
int temp;
cin >> temp;
map[temp] = i;
}
//以map为大小关系寻找a数组的最长不下降子序列
int len1 = 0;
for (int i = 1; i <= n; ++i)
{//查找
if (d1[len1] < map[a[i]])//将已经选出的数字与下一个数字比较
{
d1[++len1] = map[a[i]];
}
else
{
int p1 = lower_bound(d1 + 1, d1 + 1 + len1, map[a[i]]) - d1;
//cout << p1 << endl;
d1[p1] = map[a[i]];
}
}
cout << len1;
return 0;
}