您的位置:首页 > 编程语言 > C语言/C++

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

2013-08-05 14:59 337 查看

链接:

http://acm.hdu.edu.cn/showproblem.php?pid=1257

http://acm.hust.edu.cn/vjudge/contest/view.action?cid=28195#problem/D


最少拦截系统

TimeLimit:2000/1000MS(Java/Others)    MemoryLimit:65536/32768K(Java/Others)

TotalSubmission(s):12863    AcceptedSubmission(s):5100


ProblemDescription

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

怎么办呢?多搞几套系统呗!你说说倒蛮容易,成本呢?成本是个大问题啊.所以俺就到这里来求救了,请帮助计算一下最少需要多少套拦截系统.

 

Input

输入若干组数据.每组数据包括:导弹总个数(正整数),导弹依此飞来的高度(雷达给出的高度数据是不大于30000的正整数,用空格分隔)

 

Output

对应每组数据输出拦截所有导弹最少要配备多少套这种导弹拦截系统.

 

SampleInput

838920715530029917015865

 

SampleOutput

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的资料:
http://hi.baidu.com/freezhan/item/c681309ef81ebbd0291647ed

http://www.cnblogs.com/okboy/p/3224186.html

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

样例中的数据是:

38920715530029917015865

那么采取贪心的思路:

以数组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
20715530029917015865
使用的拦截系统:11122221

后来看到了学妹总结了这样一句话感觉很好

下面借用Teilwall的一句话:

 求最长上升子序列:

   给定排好序的一堆数列中,求其的LIS长度。它的LIS长度就是它非上升子序列的个数。

下面说一下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[]:38920715530029917015865
使用的拦截系统dp[]:11122221

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

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

其实本质上都一样了,就像这篇神奇的LIS的优化,奇怪的命名方式,看了我大半天才懂
http://hi.baidu.com/freezhan/item/c681309ef81ebbd0291647ed

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

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

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

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

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


关于lower_bound和upper_bound:
lower_bound返回的是>=查找的数的第一个下标
upper_bound返回的只是>查找的数的第一个下标

都是在数组中左闭右开区间中查找。
越界情况:如果查找的数>数组中所有的数,则返回数组尾部的下标,此时数组越界

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

code:

贪心:

DAccepted236KB15msC++746B
#include<stdio.h>
#include<string.h>
#include<algorithm>
usingnamespacestd;

constintmaxn=1000+10;
constintINF=30000+10;//导弹高度不会超过30000
inta[maxn];//存导弹的高度
inth[maxn];//h[i]表示第i个导弹系统拦截的最低高度

intmain()
{
intn;
while(scanf("%d",&n)!=EOF)
{
for(inti=0;i<n;i++)
{
scanf("%d",&a[i]);
h[i]=INF;//初始化保证每一个拦截系统都能拦截所有的导弹
}

for(inti=0;i<n;i++)
{
for(intj=0;j<=i;j++)//往前找开发了的导弹系统,看是否能拦截当前导弹,最坏的结果是每个导弹都需要一个新的导弹系统来拦截,所以遍历到i
{
if(h[j]>=a[i])//一旦找到符合条件的拦截系统
{
h[j]=a[i];//第j个拦截系统拦截了第i个导弹,更新它的目前可以拦截的导弹的高度
break;//第i个导弹已经拦截,跳出里面那层循环
}
}

}

inttot=0;
for(inti=0;i<n;i++)//计算总共用了几个导弹系统
if(h[i]!=INF)//如果第i个导弹系统的高度不等于初始值说明它用过
tot++;
printf("%d\n",tot);
}
return0;
}


DP之O(n^n)

DAccepted236KB15msC++792B
/*****************************************************
dp[i]=max(dp[i],dp[j]+1)【0<=j<i,a[i]>a[j]】
如果当前导弹i的高度>前面的导弹j的高度,
那么拦截当前导弹i的系统,一定是拦截j的后面的系统
******************************************************/
#include<stdio.h>
#include<algorithm>
usingnamespacestd;

constintmaxn=1000+10;
constintINF=30000+10;

inta[maxn];//存导弹的高度
intdp[maxn];//d[i]表示第i个导弹是被第dp[i]个拦截系统拦截的

intmain()
{
intn;
while(scanf("%d",&n)!=EOF)
{
for(inti=0;i<n;i++)
{
scanf("%d",&a[i]);
dp[i]=1;
}

for(inti=0;i<n;i++)
{
for(intj=0;j<i;j++)
if(a[i]>a[j])
dp[i]=max(dp[i],dp[j]+1);
}

intans=0;
for(inti=0;i<n;i++)
ans=max(ans,dp[i]);
printf("%d\n",ans);
}
return0;
}


Dp之O(nlogn)二分查找:

DAccepted236KB0msC++959B
#include<stdio.h>
#include<algorithm>
usingnamespacestd;

constintmaxn=1000+10;

inta[maxn];//导弹高度
inth[maxn];//h[i]表示第i个系统目前拦截的高度

intfind(inth[],intlen,intha)//返回index,数组h[]中,第一个h[index]>=ha
{
intleft=0;
intright=len;

while(left<=right)
{
intmid=(left+right)/2;

if(ha>h[mid])left=mid+1;
elseif(ha<h[mid])right=mid-1;
elsereturnmid;
}
returnleft;
}

intmain()
{
intn;
while(scanf("%d",&n)!=EOF)
{
for(inti=0;i<n;i++)
{
scanf("%d",&a[i]);
}

h[0]=-1;
h[1]=a[0];
intlen=1;

for(inti=1;i<n;i++)
{
intindex=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);
}
return0;
}


DpO(nlogn)之直接调用lower查找

DAccepted236KB0msC++814B2013-08-0511:34:41
#include<stdio.h>
#include<string.h>
#include<algorithm>
usingnamespacestd;

constintmaxn=1000+10;
inta[maxn];
inth[maxn];

intmain()
{
intn;
while(scanf("%d",&n)!=EOF)
{
for(inti=0;i<n;i++)
scanf("%d",&a[i]);
h[0]=-1;
h[1]=a[0];
intlen=1;

for(inti=1;i<n;i++)
{
intindex=lower_bound(h,h+len+1,a[i])-h;
h[index]=a[i];
//printf("test:h[%d]=%d\n",index,h[index]);
if(index>len)
len=index;

}
printf("%d\n",len);

}
return0;
}



                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  算法 语言 POJ c c++