题意
为了对抗附近恶意国家的威胁,R 国更新了他们的导弹防御系统。
一套防御系统的导弹拦截高度要么一直 严格单调 上升要么一直 严格单调 下降。
例如,一套系统先后拦截了高度为 3 和高度为 4 的两发导弹,那么接下来该系统就只能拦截高度大于 4 的导弹。
给定即将袭来的一系列导弹的高度,请你求出至少需要多少套防御系统,就可以将它们全部击落。
思路
一般的最长递增子序列问题中,系统要么只能一直递增,要么只能一直递减。而此题中是导弹拦截系统可以选择一直上升,也可以选择一直下降,当然一个系统只能二者取一。且要求击落所有导弹,单纯的最长递增子序列已经不再适用。
此题的解法是DFS最优剪枝+贪心
贪心
以最长上升子序列为例:
假设当前已经存在了n条单调上升的子序列, 每个序列最大的一个数(即最后一个数)为
d
i
d_i
di 。假设
d
i
d_i
di按从小到大的顺序排列。现在加入一个数x,最优解是:将x加入到第一个小于x的队列中。
例如:1 3 4 7 9 , x = 5,则加入到
d
i
=
4
d_i = 4
di=4 的队列中。
若没有找打这样的数,则新建一个上升序列。
DFS
- 设置两个数组:
- up:记录单调上升子序列的末尾元素,也即上面提到的 d i d_i di。此数组中的数是按从大到小的顺序排列的。
- down:记录单调下降子序列的末尾元素,也即上面提到的 d i d_i di。此数组中的数是按从小到大的顺序排列的。
- 然后暴力枚举所给的序列应该属于的序列(每个数要么是上升序列中的数,要么是下降序列中的数)。对于每个序列采用上面的提到的贪心算法。
(优化) - 若dfs过程中,当前结果已经大于等于目前的最优解,直接return
(剪枝) - 然后对于为什么up数组是从大到小排列。因为这样排列后,只需顺序循环,找到比新来的数小的一个数后即为所求(寻找第一个小于新来的数)。若没有找到,咋增加一个上升序列,即将新来的数加到up数组末尾,up数组长度加一。down数组同理。
代码
const int N = 50 + 7, M = 1e6;
int up[N], down[N], a[N];
int ans, n;
//idx:原序列的第idx个数
//u,d:up,down数组的长度
void dfs(int idx, int u, int d)
{
if (u + d >= ans) return;
if (idx == n)
{
ans = min(ans, u + d);
return;
}
//将第i个数加入到上升序列中
int k = 0;
while (k < u && up[k] >= a[idx]) k++;
int t = up[k];//备份
up[k] = a[idx];
if (k < u) dfs(idx + 1, u, d);
else dfs(idx + 1, u + 1, d);
up[k] = t;//还原现场
//将第i个数加入到下降序列中
k = 0;
while (k < d && down[k] <= a[idx]) k++;
t = down[k];
down[k] = a[idx];备份
if (k < d) dfs(idx + 1, u, d);
else dfs(idx + 1, u, d + 1);
down[k] = t;//还原现场
}
int main()
{
while (cin >> n, n)
{
for (int i = 0; i < n; i++)
cin >> a[i];
ans = n;
dfs(0, 0, 0);
cout << ans << endl;
}
return 0;
}