最少拦截系统Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 12985 Accepted Submission(s): 5171
Problem Description
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能超过前一发的高度.某天,雷达捕捉到敌国的导弹来袭.由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹.
怎么办呢?多搞几套系统呗!你说说倒蛮容易,成本呢?成本是个大问题啊.所以俺就到这里来求救了,请帮助计算一下最少需要多少套拦截系统.
Input
输入若干组数据.每组数据包括:导弹总个数(正整数),导弹依此飞来的高度(雷达给出的高度数据是不大于30000的正整数,用空格分隔)
Output
对应每组数据输出拦截所有导弹最少要配备多少套这种导弹拦截系统.
Sample Input
Sample Output
Source
Recommend
JGShining
|
====================================算法分析======================================
一、贪心算法:
显然第一想法就是贪心么,但是贪亦有道,我知道和珅之所以名扬千古的原因了。。。
在contest时我想到的贪心思路是:让每一套系统拦截尽可能多的导弹,也就是在尚未决定拦截系统的导弹序列中选取拦截其
最长不上升子序列中的导弹。
然后,然后就是WA的血与泪。
后来看题解的时候找到了这样一个反例,导弹序列为:6 5 1 7 3 2。
根据以上的贪心思路,第一套系统就应该拦截导弹 6 5 3 2 ,那剩下的导弹 1 7 就不得不另用两套系统来拦截,这样总共就需
要三套系统。
而事实上,最佳方案应该是,让一套系统拦截 6 5 1 ,一套系统拦截 7 3 2 ,总共只需两套系统。
从上面的例子可以看出,由于拦截系统之间的相互影响,让每一套系统拦截尽可能多的导弹的贪心思路是错误的。
那么正确的贪心思路呢?应该是使得拦截每一颗导弹的代价最小!
根据题意显然应该将新增一套系统作为最大代价,也就说只要已经使用的这些系统能够拦截当前导弹那就不应该再新增一套系统
来拦截。
而在已经使用的能够拦截当前导弹的系统中应该选择哪套系统来拦截呢?根据贪心思路显然应该让能够拦截的导弹高度最低的系
统来拦截。
根据以上分析可以得到这样一份算法:
用一个数组record来记录已经使用的系统能够拦截的导弹高度。
如果当前导弹高度大于record中的所有值,则新增一套系统来拦截,并将当前导弹高度(也就是之后新增系统能够拦截的导弹高
度)加入record数组。
否则在record数组中选取大于当前导弹高度的所有值中最小的那个,让其对应的系统来拦截,并更新其能够拦截的导弹高度为当
前导弹高度。
这里注意到一个关键:只要record是不降序的,那么无论是加入元素还是修改元素,都不会破坏record不降序的性质!
因而record一直是不降序的!那么之前所说的查找操作就可以通过O(logN)的二分来实现!故而算法的时间复杂度为O(N*logN)。
二、最长不下降子序列:
有没有发现上述的贪心算法其实就是求导弹序列的最长不下降子序列的O(N*logN)的算法?
但是我想了N久还是没想出从题目直接切入到最长不下降子序列的思维过程。。。
总算是找到直接切入的思维过程了,其实就是俩定理:
Dilworth定理:对于一个偏序集,链的最少划分数等于其最长反链的长度。
Dilworth定理的对偶定理:对于一个偏序集,其反链的最少划分数等于其最长链的长度。
Dilworth定理貌似比较难证,暂时就不管它了。
Dilworth定理的对偶定理的证明参见:http://blog.sina.com.cn/s/blog_9634532001019znf.html,有时间得弄懂。
如果学过离散数学,理解一下定理还是不难的(书到用时方恨少哇~)。
对于这个题目而言,偏序关系就是小于等于,偏序集就是导弹序列,链就是不上升子序列,反链就是上升子序列。
所求的导弹序列的不上升子序列的最少划分数就等于其最长上升子序列的长度!
=======================================代码=======================================
一、贪心算法。
#include<stdio.h>
int N,Height[1005];
int Greedy()
{
int sum=0,record[1005];
record[sum++]=Height[0]; //使用一套系统拦截第一颗导弹
for(int i=1;i<N;++i)
{
if(Height[i]>record[sum-1]) //新增一套系统拦截当前导弹
{
record[sum++]=Height[i]; continue;
}
int l=0,r=sum-1;
while(l<r) //贪心原理选取系统拦截当前导弹
{
int m=(l+r)>>1;
if(record[m]<Height[i]) { l=m+1; }
else { r=m; }
}
record[l]=Height[i];
}
return sum;
}
int main()
{
while(scanf("%d",&N)==1)
{
for(int i=0;i<N;++i)
{
scanf("%d",&Height[i]);
}
printf("%d\n",Greedy());
}
return 0;
}
二、最长不下降子序列(用了自己写的模板)。
/*#######################################[ LOS Templet Of Lyz ]#######################################*/
typedef bool BOOL;
typedef unsigned int UINT;
template < typename SEQUTYPE >
//CMP:返回所求序列中前后元素的比较关系("<","<=",">",">="即对应"升序","不降序","降序","不升序")
UINT LOS(SEQUTYPE *Sequ , UINT nSize , BOOL (*CMP)(SEQUTYPE*,SEQUTYPE*) )
{
UINT Len , *MinID = new UINT [ nSize + 1 ];
MinID[ Len = 1 ] = 0;
for( UINT i = 1 ; i < nSize ; ++i )
{
if( ! CMP( Sequ + MinID[1] , Sequ + i ) ) { MinID[1] = i; continue; }
if( CMP( Sequ + MinID[Len] , Sequ + i ) ) { MinID[ ++Len ] = i; continue; }
int L = 1 , R = Len;
while( L < R )
{
int M = ( L + R ) >> 1;
if( CMP( Sequ + MinID[ M + 1 ] , Sequ + i ) ) { L = M + 1; }
else { R = M; }
}
MinID[ L + 1 ] = i;
}
delete [] MinID;
return Len;
}
/*#######################################[ LOS Templet Of Lyz ]#######################################*/
#include<stdio.h>
int N,Height[1005];
bool Cmp(int *IntNum1,int *IntNum2)
{
return *IntNum1<=*IntNum2;
}
int main()
{
while(scanf("%d",&N)==1)
{
for(int i=0;i<N;++i)
{
scanf("%d",&Height[i]);
}
printf("%d\n",LOS(Height,N,Cmp));
}
return 0;
}