##[$AcWing$ $1017$. 怪盗基德的滑翔翼 ](https://www.acwing.com/problem/content/1019/) ### 零、前导知识 [$AcWing$ $895$. 最长上升子序列](https://www.cnblogs.com/littlehb/p/15425546.html) [$AcWing$ $896$. 最长上升子序列 II](https://www.cnblogs.com/littlehb/p/15429000.html) ### 一、题目描述 怪盗基德是一个充满传奇色彩的怪盗,专门以珠宝为目标的超级盗窃犯。 而他最为突出的地方,就是他每次都能逃脱中村警部的重重围堵,而这也很大程度上是多亏了他随身携带的便于操作的滑翔翼。 有一天,怪盗基德像往常一样偷走了一颗珍贵的钻石,不料却被柯南小朋友识破了伪装,而他的滑翔翼的动力装置也被柯南踢出的足球破坏了。 不得已,怪盗基德只能操作受损的滑翔翼逃脱。 假设城市中一共有$N$幢建筑排成一条线,每幢建筑的高度各不相同。 初始时,**怪盗基德可以在任何一幢建筑的顶端**。 **他可以选择一个方向逃跑,但是不能中途改变方向**(因为中森警部会在后面追击)。 因为滑翔翼动力装置受损,他**只能往下滑行**(即:只能从较高的建筑滑翔到较低的建筑)。 他希望**尽可能多经过不同建筑的顶部**,这样可以减缓下降时的冲击力,减少受伤的可能性。 请问,他最多可以经过多少幢不同建筑的顶部(包含初始时的建筑)? **输入格式** 输入数据第一行是一个整数$K$,代表有$K$组测试数据。 每组测试数据包含两行:第一行是一个整数$N$,代表有$N$幢建筑。第二行包含$N$个不同的整数,每一个对应一幢建筑的高度$h$,按照建筑的排列顺序给出。 **输出格式** 对于每一组测试数据,输出一行,包含一个整数,代表怪盗基德最多可以经过的建筑数量。 数据范围 $1≤K≤100,1≤N≤100,0
题目要求:最多可以经过多少幢不同建筑的顶部(包含初始时的建筑)?这些建筑的高度必须是单调递减的。 从中间某个位置向两边递减高度,也就是中间最高,两边低,可以转化为从两边向中间走,单调递增,即$LIS$最长上升子序列问题。 并且题目中明确:假设城市中一共有N幢建筑排成一条线,每幢建筑的高度各不相同。如此,就是一个严格上升子序列问题,可以在判断是不用写等号了。 ![](https://img2020.cnblogs.com/blog/8562/202112/8562-20211206083828329-32794817.png) ### 三、题目总结 1. 从左到右,求一遍最长上升子序列$LIS$问题。 2. 从右到左,求一遍最长上升子序列$LIS$问题。 3. 两次结果取最大值即可。 ### 四、朴素$O(N^2)$版本 ```cpp {.line-numbers} #include using namespace std; const int N = 110; int n; // 楼房的个数 int w[N]; // 楼房的高度数组 int f[N]; // LIS结果数组,DP结果 int main() { int T; cin >> T; while (T--) { // 保持好习惯,多组测试数据,记得每次清空结果数组 memset(f, 0, sizeof f); int res = 0; cin >> n; for (int i = 1; i <= n; i++) cin >> w[i]; // 从左到右,求一遍最长上升子序列[朴素O(N^2)版本] for (int i = 1; i <= n; i++) { f[i] = 1; for (int j = 1; j < i; j++) if (w[i] > w[j]) f[i] = max(f[i], f[j] + 1); res = max(res, f[i]); } // 反向求解 LIS问题 for (int i = n; i >= 1; i--) { f[i] = 1; for (int j = n; j > i; j--) if (w[i] > w[j]) f[i] = max(f[i], f[j] + 1); res = max(res, f[i]); } printf("%d\n", res); } return 0; } ``` ### 四、贪心+二分优化版本($O(NlogN)$)版本 ```cpp {.line-numbers} #include using namespace std; const int N = 110; int n; // 楼房的个数 int a[N]; // 楼房的高度数组 // 数组模拟栈 int f[N], fl; int g[N], gl; int res; int main() { int T; cin >> T; while (T--) { // 保持好习惯,多组测试数据,记得每次清空结果数组 memset(f, 0, sizeof f); memset(g, 0, sizeof g); fl = 0, gl = 0; cin >> n; for (int i = 0; i < n; i++) cin >> a[i]; // 正着求 f[0] = a[0]; for (int i = 1; i < n; i++) { if (a[i] > f[fl]) f[++fl] = a[i]; else *lower_bound(f, f + fl, a[i]) = a[i]; } // 前半程的结果 res = fl; // 反着求 g[0] = a[n-1]; for (int i = n - 1; i >= 0; i--) { if (a[i] > g[gl]) g[++gl] = a[i]; else *lower_bound(g, g + gl, a[i]) = a[i]; } // pk的最大结果 res = max(res, gl); // 输出 printf("%d\n", res + 1); } return 0; } ``` ### 五、经验总结 $LIS$序列不唯一,$LIS$长度唯一 举的栗子来讲,给出序列 $( 1, 7, 3, 5, 9, 4, 8)$,易得最长上升子序列长度为$4$,这是确定的,但序列可以为 $(1, 3, 5, 8)$, 也可以为 $(1, 3, 5, 9)$。 ### 六、疑问 都排在一条直线了,遇到某个比自己高的,滑翔翼也飞不过去啊,这提供出来的$LIS$长度似乎无法成为侠盗基德的理论依据啊,他要是按这个数来跑,估计会卡在某个建筑上方,剩下的就哭吧,这道题的描述有问题,应该修改为 **可以在遇到比自己高的建筑时,绕到更矮的建筑上** 才合理。