题解:
导弹防御系统很自然的想到LIS算法,不过这里的条件是一套防御系统的导弹拦截高度要么一直上升要么一直下降,所以用LIS算法是不正确的
而LIS中,最核心的思想在于能否将一个元素加入到序列中,只与这个序列目前的最后一个元素有关
这道题就用了这个关键的思想。
用up[k]和down[k]记录第k套上升(下降)系统目前所拦截的最后一个导弹
dfs(u,v,t)意味着已有u个上升,v个下降,正在处理第t个数
按理说,每拿到一个新的数字应该将它所有能放入的序列都放一遍的
但扩展节点时却存在一个贪心策略,大大节省了时间。
假设现在要把一个数放入一个上升序列,那么一定是所有能放入的上升序列中,最后一个元素最大的那一个。
其实想想也是,既然每个数字都要放到一个序列中,
对于上升序列,肯定是目前越小越有用,既然能放入大的里面,何必浪费一个小的呢
注意到其实up[i]按这种策略已经是排好序的了,所以只用找最先碰到的一个就行了
C++ 代码
#include<iostream>
using namespace std;
const int N = 55;
int a[N], ans, up[N], down[N], n;
void dfs(int u, int d, int t) //u表示上升的系统个数,d表示下降的系统个数,t表示第t个数
{
if(u + d >= ans) return ;
if(t == n){
if(u + d < ans)ans = u + d;
return ;
}
int i;
for(i = 1; i <= u; i++) //找到第一个末尾数小于a[t]的导弹系统
if(up[i] < a[t])break;
int temp = up[i];
up[i] = a[t];//添加到该导弹系统中
dfs(max(u, i), d, t + 1);
up[i] = temp; //恢复现场
for(i = 1; i <= d; i++)//找到第一个末尾数大于a[t]的导弹系统
if(down[i] > a[t])break;
temp = down[i];
down[i] = a[t];//添加到该导弹系统中去
dfs(u, max(d, i), t + 1);
down[i] = temp;//恢复现场
}
int main()
{
while(scanf("%d", &n) != EOF && n != 0){
ans = 100;
for(int i = 0; i < n; i++)
cin >> a[i];
dfs(0, 0, 0);
printf("%d\n", ans);
}
return 0;
}
看完题解感觉这个题好简单啊🤣
大佬,想问一下,为什么说“up[i]按这种策略已经是排好序的了”,为啥有序啊
比方说,我们创造up新序列的时机就是已有序列的最后一个数都大于当前要插入的数,即新序列的最后一个数一定是小于所有序列的最后一个数,那么从一开始,第二个序列的最后一个数小于第一个序列的,以此类推,所有已有的up序列就按照最后一个数的大小从大到小排列了。
你被点名啦
这里补充一下大佬的剪枝思路, 一开始我整体分了4大类;
1.放入up;
2放入down;
3新开一个up;
4新开一个down;
这样也能过, 但是大佬直接分成了两种情况
1.放入up, 如果拦截不了, 开一个新up;
2放入down, 如果拦截不了, 开一个新的down;
具体逻辑就是, 如果你的up能够拦截, 就一定不需要再开一个up;
例如 假设你的up[1, 2, 3]是4, 3, 2, 现在来了个6, 我们放在up[1]的位置就变成了6,3,2
而开一个新的up就变成了4, 3, 2, 6;
这样的话第一,新的拦截系统不符合我们贪心思路的有序,
第二,我们分情况讨论,如果接下来的导弹都比6小, 根本用不到6, 就相当于多开, 如果有比6大的, 要一直到拦截系统前面的数都比6大, 6才会发挥作用, 假设6可以发挥作用时, 需要拦截的数是x,如果之前6在4后面, 那么这个大数就会是一个新的拦截系统, 如果在6被新开了, 那么这个大数就在6后面, 这样的话就没有任何区别
用up[k]和down[k]记录第k套上升(下降)系统目前所拦截的最后一个导弹,这句话很关键,瞬间懂了.感谢题解!
妙啊
为什么放到一个已有的序列里它的结果是最优的啊
赞赞赞
orz !!!个人认为这题解答最清晰易懂的,思路相当nb
赞
orz
nb!
你这个写得好,原理更加清晰
niubi
大佬 想问一下这里的剪枝具体是什么意思啊
if(u + d >= ans) return ; 这句话怎么理解呢
我们需要防御系统个数尽量少,一旦我们发现已经搜到的合法结果中有防御系统比当前防御系统个数更少的,那么当前方案就可以pass了,它必定不是最优解
解惑了, 感谢大佬!!
能帮到你就好
我去 点了! 我还以为是u + d >= 数字的总个数 我是说没min咋取得最小值 找答案找半小时了 感谢佬!!!
stO Or2
大佬们,请教下为什么要取max(u, i) ,不是很理解 ..........
因为如果没找打符合小于或大于他的数,需要新开一个位置,如果没找到循环完后i的值恰好也比u大一,相当于往后新开了一个位置,此时的u就会变成i维持这个数组的最大值
所以说他把i定义在for的外面
抱歉我语文不是特别好希望能帮到你
哦哦我明白了,谢谢你 (●’◡’●)
精辟
精辟
为什么把u+d>=ans的等号去掉就过不了了?
数据问题吧,>的话TLE主要卡在最大那个数据,>=的话可以多省一点时间,猜测的话哈🤣
好像有道理诶
剪枝啊
女少口阿
大佬们想问下 放到上升序列中 为啥不是放到upi+1呀
理解一下这个题中
up[i]
的含义,up[i]
是指按照贪心法构造的第i
个上升子序列的末尾数字。我们用
for
循环找当前的a[t]
应该放到哪个子序列后面,有两种情况:a[i]
放到某个序列的后面,这个时候只要更新这个序列的末尾数字即可,此时i
就是这个序列的位置标记a[i]
需要新开一个序列,同样i
就是这个序列的位置标记,只要up[i] = a[t]
即可所以不论需不需要新开序列,都是
up[i] = a[t]
找不到的情况也包含在里面了吗