题目地址:
https://www.acwing.com/problem/content/189/
为了对抗附近恶意国家的威胁,某国更新了他们的导弹防御系统。一套防御系统的导弹拦截高度要么一直严格单调上升要么一直严格单调下降。例如,一套系统先后拦截了高度为 3 3 3和高度为 4 4 4的两发导弹,那么接下来该系统就只能拦截高度大于 4 4 4的导弹。给定即将袭来的一系列导弹的高度,请你求出至少需要多少套防御系统,就可以将它们全部击落。
输入格式:
输入包含多组测试用例。对于每个测试用例,第一行包含整数 n n n,表示来袭导弹数量。第二行包含 n n n个不同的整数,表示每个导弹的高度。当输入测试用例 n = 0 n=0 n=0时,表示输入终止,且该用例无需处理。
输出格式:
对于每个测试用例,输出一个占据一行的整数,表示所需的防御系统数量。
数据范围:
1 ≤ n ≤ 50 1≤n≤50 1≤n≤50
其实就是问一个数组最少能分成多少个严格单调子序列。对于只分成严格上升子序列的情况,可以用反链分解定理来做,其定理和算法参考https://blog.csdn.net/qq_46105170/article/details/108616895。但是这里需要分类成两种子序列,只能用DFS来暴力枚举每个数 x x x是应该加到上升的子序列里还是下降的子序列里。代码如下:
#include <iostream>
using namespace std;
const int N = 35;
int n;
int a[N];
// up存的是严增子序列分解的每个序列的最后一个数,
// down是严降子序列分解的每个序列的最后一个数
int up[N], down[N];
int res;
void dfs(int u, int su, int sd) {
// 如果深度大于等于了之前找到的解,那就没必要搜了
if (su + sd >= res) return;
// 搜到一个解,则更新答案
if (u == n) {
res = su + sd;
return;
}
// 二分严增子序列末尾,找到第一个小于a[u]的数的位置(up数组是单调降的)
int l = 0, r = su - 1;
while (l < r) {
int m = l + (r - l >> 1);
if (up[m] < a[u]) r = m;
else l = m + 1;
}
// 找不到则新开一个序列,继续搜索;否则覆盖掉二分找到的位置,继续搜索
if (l > r || up[l] >= a[u]) {
up[su++] = a[u]; dfs(u + 1, su, sd); su--;
} else {
int t = up[l]; up[l] = a[u]; dfs(u + 1, su, sd); up[l] = t;
}
// 类似上面,down数组是单调增的,找到第一个大于a[u]的位置
l = 0, r = sd - 1;
while (l < r) {
int m = l + (r - l >> 1);
if (down[m] > a[u]) r = m;
else l = m + 1;
}
if (l > r || down[l] <= a[u]) {
down[sd++] = a[u]; dfs(u + 1, su, sd); sd--;
} else {
int t = down[l]; down[l] = a[u]; dfs(u + 1, su, sd); down[l] = t;
}
}
int main() {
while (cin >> n, n) {
for (int i = 0; i < n; i++) cin >> a[i];
res = n;
dfs(0, 0, 0);
cout << res << endl;
}
return 0;
}
时间复杂度 O ( 2 n log n ) O(2^n\log n) O(2nlogn)(实际并没有这么多,看具体数据),空间 O ( n ) O(n) O(n)。