有志者,事竟成,破釜沉舟,百二秦关终属楚。
苦心人,天不负,卧薪尝胆,三千越甲可吞吴。
P1020 导弹拦截
题目链接:
https://www.luogu.com.cn/problem/P1020
题目解释:
首先就是要找到一个最长非上升子序列,这是第一问,第二问就是非上升序列有多少个。
问题分析:
就是优化最长非上升子序列的问题,要求时间复杂度 n l o g n nlog_n nlogn, n 2 n^2 n2的时间复杂度会爆,所以得优化。
题目思路:
首先新建一个数组dp[],和变量int,分别用来记录非上升子序列,和当前最长非上升子序列的长度。
看样例:
389 207 155 300 299 170 158 65
首先存在最长非上升子序列:389 300 299 170 158 65, 长度为6
其中207 155,这个序列需要第二套设备
所以答案就是6 2
首先开始手动模拟
记住这是动态规划题目,dp数组只需要存储一种状态而不是准确的答案。
dp数组中存储一个非上升子序列,当出现一个小于 d p [ l e n ] dp[len] dp[len]的数字的时候,就执行 d p [ + + l e n ] = a [ i ] dp[++len] = a[i] dp[++len]=a[i],这时表明,当前最长长度发生变化,更新当前最长长度的最小值。
如果出现一个大于 d p [ l e n ] dp[len] dp[len]的数字 a [ i ] a[i] a[i]的时候,就使用二分查找,在dp数组中找到一个大于 a [ i ] a[i] a[i]的位置,然后把那个位置的数字替换为 a [ i ] a[i] a[i]。
这意味着,如果后面出了刚好比 a [ i ] a[i] a[i]小的序列,我就可以把原来的dp数组给替换了,而替换的过程len的大小并不变。如果到时候len值变化了,说明一定有更长的新的序列出现了。如果我一直在替换dp数组中的,但是len值没变化,其实这个时候就相当于,我取得最长非上升子序列还是len值刚刚更新时的那个dp数组。
第一个问题解决。
第二个问题:
怎么解决多个非上升子序列:
和上一问思路差不多。新建一个数组dp2[],然后只要有小的数字出现,就替换,如果有大的数字出现,就加在后面,有小的数字出现即 d p 2 [ l e n 2 ] > = a [ i ] dp2[len2] >= a[i] dp2[len2]>=a[i],说明当前的设备可以满足,设备数量并不需要满足,如果有大的数字出现即 d p 2 [ l e n 2 ] < a [ i ] dp2[len2] < a[i] dp2[len2]<a[i],说明当前的高度已经无法满足,需要另外一套新的设备,套用上一问的思路,直接二分就可以。
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7;
int f[N], d1[N], d2[N];
int main(){
ios::sync_with_stdio(false);
int n = 0, len1 = 1, len2 = 1;
while (cin >> f[++n]); n--;
d1[1] = d2[1] = f[1];
for (int i = 2; i <= n; i++){
//只要高度比当前设备最后一次的高度低,那就可以继续使用
if (d1[len1] >= f[i]) d1[++len1] = f[i];
else{
int p = upper_bound(d1 + 1, d1 + 1 + len1, f[i], greater<int> ()) - d1;
d1[p] = f[i];
}
//只要高度不提高,就不需要另外一套设备
if (d2[len2] < f[i]) d2[++len2] = f[i];
else{
int p = lower_bound(d2 + 1, d2 + 1 + len2, f[i]) - d2;
d2[p] = f[i];
}
}
cout << len1 << " " << len2 << endl;
return 0;
}
需要注意两个函数:
lower_bound(),找到第一个大于等于的数的位置,返回值为指针,并不是下标所以要减去首地址。
upper_bound(),找到第一个大于的数的位置,返回值同样为指针。