hdu 1257 最少拦截系统【贪心 || DP——LIS】

时间:2022-08-18 19:12:44

链接:



最少拦截系统

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)

Total Submission(s): 12863    Accepted Submission(s): 5100

Problem Description
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能超过前一发的高度.某天,雷达捕捉到敌国的导弹来袭.由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹.

怎么办呢?多搞几套系统呗!你说说倒蛮容易,成本呢?成本是个大问题啊.所以俺就到这里来求救了,请帮助计算一下最少需要多少套拦截系统.
 
Input
输入若干组数据.每组数据包括:导弹总个数(正整数),导弹依此飞来的高度(雷达给出的高度数据是不大于30000的正整数,用空格分隔)
 
Output
对应每组数据输出拦截所有导弹最少要配备多少套这种导弹拦截系统.
 
Sample Input
8 389 207 155 300 299 170 158 65
 
Sample Output
2
 
Source
 
Recommend
JGShining
 



算法:贪心 || Dp 【本质一样】


思路:

算是很简单的一道题目了,但是从这里 学会了所谓的 LIS 优化,所以想好好总结下了

开始是用贪心做的了,复杂度 O(n^2)最容易理解的一种写法了。
后来看了下这篇博客的分析:说是用“最长上升子序列的长度”http://www.cnblogs.com/dgsrz/articles/2384081.html
复杂度 O(n^2)高了点,还是比较好理解的了。

/*
最长上升子序列(一维形式DP)
opt[i]=max(opt[j])+1, opt[i]) (0<=j<i 且num[j]<=num[i]) {最长非下降序列}
opt[i]=max(opt[j])+1, opt[i]) (0<=j<i 且num[j]>num[i]) {最长下降序列}
该算法复杂度为O(n^2)
*/

但是这样就还不如贪心了效率。后来又去找了下 LIS 的资料:


发现 LIS 的 O(nlogn)的算法其实和贪心的本质很像,下面就以这一题仔细总结下。


样例中的数据是:

389 207 155 300 299 170 158 65

那么采取贪心的思路:


以数组 h[] 记录拦截系统当前的拦截高度,先初始化为最大值 INF = 30000+10,
表示每一个新拦截系统都能拦截所有的导弹,然后遇到一个导弹就往前找看是否有已经使用了的系统能拦截,如果有,直接用;否则重新弄一个系统。最后再看用了几个系统就好了。

第一个导弹 389 < h[1] ( h[1] = INF)被第一个系统拦截 h[1] = 389
第二个导弹 207 < h[1] 被第一个系统拦截 h[1] = 207
第三个导弹 155 < h[1] h[1] = 155
第四个导弹 300 > h[1] , 300 < h[2] ( h[2] = INF ) 所以新开发一个系统拦截第四个导弹, h[2] = 300
第五个导弹 299 > h[1] , 299 < h[2] 被第二个系统拦截 h[2] = 299
第六个导弹 170 > h[1] , 170 < h[2] h[2] = 170
第七个导弹 158 > h[1] , 158 < h[2] h[2] = 158
第八个导弹 65 < h[1] 被第一个系统拦截 h[1] = 65

所以最后使用了两个系统就拦截了所有的导弹【遍历 h[]数组从 1到 n 看有几个 != INF 就说明使用了】

导弹高度:389
207 155 300 299 170 158 65
使用的拦截系统: 1 1 1 2 2 2 2 1


下面说一下DP的 O(n^n)的思路:


dp[i] 记录的是拦截第 i 个导弹的系统的编号 index
先初始化所有的导弹都被第一个系统拦截。dp[i] = 1

dp[i] = max(dp[i], dp[j]+1) 【0<=j<i && high[i] > high[j]】
当遇到第 i 个导弹的时候,看前面已经拦截了的导弹的系统是否能够拦截第 i 个导弹 0 <= j < i
一旦第 i 个导弹比第 j 个导弹要高,那么依照上面贪心的思路,拦截第 i 个导弹的系统必定比拦截第 j 个导弹的系统大,
最少大一个【如果 (j < k < i) 如果第 k 个导弹没有比第 i 个低的】
(不知道说清楚了没有,反正我是这么理解的了)
那么最后出的 dp[]对应的值将是上面贪心的思想的 h[]的下标

导弹高度 a[]:389 207 155 300 299 170 158 65
使用的拦截系统 dp[]: 1 1 1 2 2 2 2 1

最后输出最大的 dp[] 就是所需系统的个数了

最后总结下 Dp 的 O (nlogn)的思路:


其实本质上都一样了,就像这篇神奇的 LIS 的优化,奇怪的命名方式,看了我大半天才懂

首先第一轮遍历所有的导弹,是不能改了的这里消耗 O(n)
然后就是把第二重循环改成了二分而已 ,二分复杂度 O(logn)

首先我们要明确的是上面 Dp O(n^2) LIS 思想的第二轮找的是什么?

首先还是先定义一个 h[] 数组存储拦截系统的高度,那么根据前面贪心的分析:
我们可以明确这一点只要是用过了的系统 h[] 那么它一定是单调递增
这个时候 h[] 保存的就是遍历到第 i 个导弹的时候,当前使用过的系统目前能够拦截导弹的最高值

所以第二轮找的是前面第一个 h[index] >= a[i] ,然后再更新 h[index]= a[i]
永远维护 h[]是单调递增的,最后输出 h[]的使用长度,或是像上面贪心一样遍历一遍数出用了几个 h[] 都可以

那么再回到我们的关键问题:如何使得第二轮的顺序查找 O(n) 优化成 O(logn)
注意到:h[]永远是单调递增的,那么直接写个二分查找就可以了。基本上对于这题可以 0 ms 秒过,
最终发现这样也是网上传的 LIS 的最优的解法
如果你想偷懒:那么二分也不用写了,直接加个 algorithm 的头文件了,调用 upper_bound就好了【kuangbin大神教的Orz】
int index = upper_bound(h,h+len+1,a[i])-h; //保证 h[index] 是数组 h 中第一个 >= a[i] 的

下面依次贴代码,看不懂的输出中间变量就好了。


code:


贪心:


D Accepted 236 KB 15 ms C++ 746 B


#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std; const int maxn = 1000+10;
const int INF = 30000+10; //导弹高度不会超过 30000
int a[maxn]; //存导弹的高度
int h[maxn]; // h[i] 表示第 i 个导弹系统拦截的最低高度 int main()
{
int n;
while(scanf("%d", &n) != EOF)
{
for(int i = 0; i < n; i++)
{
scanf("%d", &a[i]);
h[i] = INF; //初始化保证每一个拦截系统都能拦截所有的导弹
} for(int i = 0; i < n; i++)
{
for(int j = 0; j <= i; j++) //往前找开发了的导弹系统,看是否能拦截当前导弹, 最坏的结果是每个导弹都需要一个新的导弹系统来拦截,所以遍历到 i
{
if(h[j] >= a[i]) //一旦找到符合条件的拦截系统
{
h[j] = a[i]; // 第 j 个拦截系统拦截了第 i 个导弹 , 更新它的目前可以拦截的导弹的高度
break; //第 i 个导弹已经拦截,跳出里面那层循环
}
} } int tot = 0;
for(int i = 0; i < n; i++) //计算总共用了几个导弹系统
if(h[i] != INF) //如果第 i 个导弹系统的高度不等于初始值说明它用过
tot++;
printf("%d\n", tot);
}
return 0;
}



DP之 O(n^n)


D Accepted 236 KB 15 ms C++ 792 B


/*****************************************************
dp[i] = max(dp[i], dp[j]+1) 【0<=j<i, a[i] > a[j]】
如果当前导弹 i 的高度 > 前面的导弹 j 的高度,
那么拦截当前导弹 i 的系统,一定是拦截 j 的后面的系统
******************************************************/
#include<stdio.h>
#include<algorithm>
using namespace std; const int maxn = 1000+10;
const int INF = 30000+10; int a[maxn]; //存导弹的高度
int dp[maxn]; //d[i] 表示第 i 个导弹是被第 dp[i] 个拦截系统拦截的 int main()
{
int n;
while(scanf("%d", &n) != EOF)
{
for(int i = 0; i < n; i++)
{
scanf("%d", &a[i]);
dp[i] = 1;
} for(int i = 0; i < n; i++)
{
for(int j = 0; j < i; j++)
if(a[i] > a[j])
dp[i] = max(dp[i], dp[j]+1);
} int ans = 0;
for(int i = 0; i < n; i++)
ans = max(ans, dp[i]);
printf("%d\n", ans);
}
return 0;
}





Dp之 O(nlogn) 二分查找:


D Accepted 236 KB 0 ms C++ 959 B


#include<stdio.h>
#include<algorithm>
using namespace std; const int maxn = 1000+10; int a[maxn]; //导弹高度
int h[maxn]; // h[i] 表示第 i 个系统目前拦截的高度 int find(int h[], int len, int ha) //返回 index , 数组h[] 中, 第一个h[index] >= ha
{
int left = 0;
int right = len; while(left <= right)
{
int mid = (left+right) / 2; if(ha > h[mid]) left = mid+1;
else if(ha < h[mid]) right = mid-1;
else return mid;
}
return left;
} int main()
{
int n;
while(scanf("%d", &n) != EOF)
{
for(int i = 0; i < n; i++)
{
scanf("%d", &a[i]);
} h[0] = -1;
h[1] = a[0];
int len = 1; for(int i = 1; i < n; i++)
{
int index = find(h,len, a[i]);
h[index] = a[i];
//printf("test : h[%d] = %d\n", index, h[index]);
if(index > len)
len = index;
}
printf("%d\n", len);
}
return 0;
}

Dp O (nlogn)之直接调用upper查找


D Accepted 236 KB 0 ms C++ 814 B 2013-08-05 11:34:41

#include<stdio.h>
#include<algorithm>
using namespace std; const int maxn = 1000+10;
const int INF = 30000+10;
int a[maxn]; //导弹高度
int h[maxn]; // h[i] 表示当前第 i 个系统拦截的高度 int main()
{
int n;
while(scanf("%d", &n) != EOF)
{
for(int i = 0; i < n; i++)
{
scanf("%d", &a[i]);
// h[i] = INF;
} h[0] = -1;
h[1] = a[0];
int len = 1; for(int i = 1; i < n; i++)
{
int index = upper_bound(h,h+len+1,a[i])-h; //保证 h[index] 是数组 h 中第一个 >= a[i] 的
h[index] = a[i];
//printf("test: h[%d] = %d\n", index, h[index]);
if(index > len)
len = index;
}
//for(int i = 0; i <= n; i++) printf("%d ", h[i]); printf("\n");
printf("%d\n", len);
}
return 0;
}