如何用O(nlogn)的的速度求解最长上升(下降)子序列

打导弹解题有感

求一个序列的最长上升子序列,可以用贪心的思想来做。

例:389 207 155 300 299 170 158 65

要求它的最长递减子序列,具体步骤如下:

389

389 207

389 207 155

389 300 155     //207被300替换了,207是三个数中刚好小于300的数,207能接的后序列,300照样能接,而且有更大的空间可以接

389 300 299     //此次用299替换155,一样是因为有更大的空间可用,在这里就体现了用300来替换207的正确性

389 300 299 170

389 300 299 170 158

389 300 299 170 158 65

这样就得到了一个最长递减子序列,而且更有可能接更多的数

由于在这个贪心的过程中,辅助数组是有序的,就可以用二分查找来优化,最终将时间优化到O(nlogn)

对于打导弹这道题,求最少的导弹拦截系统的个数,实践最终证明,他等价于求最长上升子序列,类似于对偶优化问题,看它的贪心过程就可知道,它是如何等价于求最长上升子序列的,过程如下:

389

207

155

155 300

155 299

155 170

155 158

56 158    // 找到刚大于56的,然后替换掉,是最优的选择

代码如下:

代码
   
     
#include < stdio.h >
#define NN 10004

int tmp[NN], high[NN];
int index;
int find1( int t){
int low = 0 ;
int hig = index - 1 ;
int ans = - 1 ;
do
{
int mid = (low + hig) / 2 ;
if (tmp[mid] < high[t]){
ans
= mid;
hig
= mid - 1 ;
}
else {
low
= mid + 1 ;
}
}
while (low <= hig);
return ans;
}
int find2( int t){
int low = 0 ;
int hig = index - 1 ;
int ans = - 1 ;
do
{
int mid = (low + hig) / 2 ;
if (tmp[mid] >= high[t]){
ans
= mid;
hig
= mid - 1 ;
}
else {
low
= mid + 1 ;
}
}
while (low <= hig);
return ans;
}
int main()
{
int i, n, t;
while (scanf( " %d " , & n) != EOF){
if (n == 0 ) break ;
for (i = 0 ; i < n; i ++ ){
scanf(
" %d " , & high[i]);
}

tmp[
0 ] = high[ 0 ];
index
= 1 ;
for (i = 1 ; i < n; i ++ ){
t
= find1(i);
if (t == - 1 ){
tmp[index
++ ] = high[i];
}
else
tmp[t]
= high[i];
}
printf(
" %d\n " , index);

tmp[
0 ] = high[ 0 ];
index
= 1 ;
for (i = 1 ; i < n; i ++ ){
t
= find2(i);
if (t == - 1 ){
tmp[index
++ ] = high[i];
}
else
tmp[t]
= high[i];
}
printf(
" %d\n " , index);
}
return 0 ;
}

 

补充:这种方法只能求得长度,如果要输出这个序列,还需要另外的算法。sgu199还是不能做!!!!

你可能感兴趣的:(log)