导弹防御系统【dfs】
题目链接
引言(请忽略):今天是小白ACM集训的日子,然后数据结构实在是太难了,真是学不动了,然后就只能无助地去复习以前的题了,记得以前在SDUT程设二里面有一道题叫最少拦截系统,它是一道贪心,我个人觉得最长上升子序列的思路跟它有些相似。我们先分析一下这道题,引入一下这道最少拦截系统的题干和代码:
最少拦截系统
Description
提炼一下题干,就是飞来一些导弹,需要用炮弹系统依次拦截,它的第一发炮弹能够到达任意的高度,但以后每一发炮弹都不能超过前一发的高度.比如如果飞来一颗比之前所有导弹都高的导弹,你只能增加一个拦截系统。求拦截系统个数
Input
输入包括:导弹总个数(正整数),导弹依此飞来的高度(雷达给出的高度数据是不大于30000的正整数,用空格分隔)
Output
对应输出拦截所有导弹最少要配备多少套这种导弹拦截系统.
Sample
Input
8 389 207 155 300 299 170 158 65
Output
2
#include <stdio.h>
#include <string.h>
int a,i;//用来保存导弹的高度
int b[100000];//用来保存拦截系统能够拦截的高度
int main()
{
int n,cnt;//n代表总共的导弹的个数,cnt代表拦截系统的个数
while(scanf("%d",&n)!=EOF)
{
memset(b,0,sizeof(b));//b用来保存拦截系统能够拦截的最大高度
cnt=1;
for(i=0;i<n;i++)
{
scanf("%d",&a);
if(i==0)
b[0]=a;
int j;
for(j=0;j<cnt;j++)//每次都用较小的高度来替换b中的数值
{
if(b[j]>=a)//如果拦截系统能够拦截就拦截
{
b[j]=a;
break;
}
}
if(j==cnt)//如果拦截系统都不能够拦截,就只能够再重新用一个拦截系统了
{
b[cnt++]=a;
}
}
printf("%d
",cnt);
}
return 0;
}
这道题就是一道在已有的拦截系统里查找的题,因为每一套拦截系统在导弹飞来之时会判断能否拦截,并更新每一套拦截系统可拦截的最大高度。最后把计数的cnt输出即可,是一道贪心策略的题目,感觉难度还可以。
但是今天这道题目可真是给我这个小白难坏了。
187. 导弹防御系统
描述
为了对抗附近恶意国家的威胁,R国更新了他们的导弹防御系统。
一套防御系统的导弹拦截高度要么一直 严格单调 上升要么一直 严格单调 下降。
例如,一套系统先后拦截了高度为3和高度为4的两发导弹,那么接下来该系统就只能拦截高度大于4的导弹。
给定即将袭来的一系列导弹的高度,请你求出至少需要多少套防御系统,就可以将它们全部击落。
输入格式
输入包含多组测试用例。
对于每个测试用例,第一行包含整数n,表示来袭导弹数量。
第二行包含n个不同的整数,表示每个导弹的高度。
当输入测试用例n=0时,表示输入终止,且该用例无需处理。
输出格式
对于每个测试用例,输出一个占据一行的整数,表示所需的防御系统数量。
数据范围
1≤n≤50
输入样例:
5
3 5 2 4 1
0
输出样例:
2
样例解释:
对于给出样例,最少需要两套防御系统。
一套击落高度为3,4的导弹,另一套击落高度为5,2,1的导弹。
题解:这道题网上大佬的解析很多,我作为小白也没有什么太深入的理解,可能有些错误认识。这里仅仅记录自己的一个学习过程。
首先我们看题,可以知道,有的导弹可以上升,也有的可以下降,所以我们不能像上道题那样,仅仅去记录多少组上升子序列的问题。
我想到的就是枚举然后去暴搜;
一个导弹的高度是应该放在递增的序列还是递减的序列,然后放到哪一个递增或者递减序列之中,就是要核心讨论的问题。
这个题对时间复杂度有要求,所以在搜索的过程要时刻想办法优化,在适当的地方去剪枝。
搜索:
- 第一种搜索可能就是BFS,宽度优先,网上有大佬用这种方法,但介于内存储存的一些写法我不太明白,就不多说了;
- 第二种就DFS,就是我采用的方法,因为在剪枝的过程确实很好写,我就只能想到这个方法了,然后可以采用声明全局最小值记录和迭代深搜两种思路,时间复杂度都是n*2^n,所以我就用前者来写这个代码。
因为小白也不怎么会,可能注释写的比较繁琐,代码如下:
#include <iostream>
using namespace std;
const int N = 100;
int n;
int q[N];//q:依次飞来的导弹;
int up[N],down[N];
int ans; // 记录全局最小变量;
void dfs(int x,int su,int sd) // x:当前枚举到哪个数,su:上升子序列的个数,sd:下降子序列的个数
{
if(su + sd >= ans) return ; // 剪枝---此时不可能再更新了,return
if(x==n) // 递归结束
{
ans = su + sd;//上升子序列和下降子序列个数和为答案
return ;
}
// 情况1:将当前数放到上升子序列中
int k =0;//k用来遍历当前所有上升子序列的末尾值
while(k < su && up[k] >= q[x]) k++; // 因为序列下标是从0开始的,所以是<
int t=up[k]; // 为后边回溯标记当前遍历;
up[k] = q[x];//如果q[u]比末尾值大,则更新up数组;
if(k < su) dfs(x+1,su,sd); // 如果被更新---即不用另开一个上升子序列m
//如果k>=su说明q[当前]小于所有上升子序列
else dfs(x+1,su+1,sd);//则su+1 (增加一个系统),继续深搜
up[k] = t; // 回溯之前的状态
//情况2与上同理;
// 情况2:将当前数放到下降子序列中
k = 0;
while(k < sd && down[k] <= q[x]) k ++;
t = down[k];
down[k] = q[x];
if(k < sd) dfs(x+1,su,sd);
else dfs(x+1,su,sd+1);
down[k] = t;
}
int main()
{
while(cin >> n&& n)
{
for(int i=0;i<n;i++)
cin >> q[i];
ans=n;
dfs(0,0,0);
cout<<ans<<endl;
}
return 0;
}
这个题我觉得对我来说是挺难的,也借鉴了许多网上大佬的思路,仅用来记录自己的学习过程吧。
新手上路,多多包涵。