这题挺简单。
看图就懂意思了,求不相交的线数目。
什么时候两条线交叉呢? --- i > j 但 a[i] < b[j] 的时候。
由于题目中a[i] = i ;
那就简单地成为了一个经典 ” 最长上升子序列 “问题啦。
数据到了w的级别,用 O(nlgn)的贪心方法做。
具体怎么做呢?其实是用了额外的一个数组stk,其中stk[0]存下当前stk的长度,我们的目标是维护stk是递增的
每次取待求数组的元素 signals[i], 将其与stk的最后一位相比较,如果比它大,则将它加入stk,当前stk的长度就是此时此刻的LIS
那么如果比stk的最后一位小,那么当然无法将其加入stk的末尾了。那么此时把signal加入到stk中去替换掉“第一个出现比signal大的数”
依次这么扫描待求数组并作处理,最后stk的长度就是LIS的长度了。
至于为什么这么做,请允许我为《算法引论》做个小解读吧(纯粹个人意见,可能错得离谱)。
回顾我们想要的目标——最长上升子序列,下称LIS ( Longest Increasing Sequence)
归纳假设:假设我们知道如何对前m个数求其长度为k(k从1开始递增到最长)的最有潜力的LIS,
最有潜力——什么意思呢?也就是更方便我们后面读到数之后加入其中。
那么它必须是长度为k的LIS里(可能有多个),结尾数最小的。
那么当处理第m+1个数的时候,我们有很多种处理情况——
1.首先如果它比最长的那个LIS(比如它的长度是x?)的尾巴还大,那么可以直接加入末尾,得到一个新的长度为x+1的一个LIS(原来那个长度x的LIS还是以原来的尾巴结尾,跟这个不矛盾)。
2.如果不是,那么我们逐次去看长度为(x-1, x-2, 。。。1)的LIS,看能否加入其中的某个,组成一个新的。
来了——如果可以加入,那么组成一个新的,势必会跟原来的某个LIS长度相等了,这时他们就要“竞争”了,PK掉一个,PK掉那个尾巴大的,留下尾巴小的,那么可以给未来更大的可能。
于是在这种情况下,我们可以知道,每次我们总能找到一个LIS,然后去递增它的长度,right?如果实在不行,那么它自己成为一个长度为1的LIS,并理所当然地PK掉其他长度为1的LIS(反正:如果不能PK掉它,那说明它比那个大,那么可以接在后面组成长度2的LIS,对吧)。
当我们把所有元素扫描完,我们自然就得到了最长的LIS了,而且保证不会错过任何一个可能(就是那些本来不是最长,后来慢慢增长并超越冠军成为最长的LIS)。
观察到我们上面只提到这些LIS的“尾巴”,于是我们只需要存下“尾巴”们和它们代表的LIS的长度就够了。
那么用一个数组完全可以做到这一点,stk[i] 表示 长度为 i 的LIS现在的尾巴。
现在我们明白stk中“替换”的操作,其实就是“PK掉尾巴大的数”的过程了。
-这里可能有个小疑问——为什么长度 k+1 的LIS的尾巴,一定不会比 长度 k 的LIS小呢?(为什么stk会是递增的)
因为如果不是这样,那么 长度 k 的那个,完全可以拿这个k+1的前k个去当LIS呀。
现在,我们得到了一个完整的算法了。由于stk有序,每次可以直接找到它该去的位置,二分找。
代码:
#include <iostream>
#include <stdio.h>
using namespace std ;
int main () {
int t,p ;
// s 是容器 , a是收到的信号(图)
int stk[40005] , signals[40005] ;
int low , high , mid ;
scanf ( "%d" , &t ) ;
while ( t-- ) {
scanf ( "%d" , &p ) ;
for ( int i = 0 ; i < p ; ++i ) scanf ( "%d" , &signals[i] ) ;
stk[0] = 1 ;
stk[1] = signals[0] ;
for ( int i = 1 ; i < p ; ++i ) {
// 二分查找容器中第一个比a[i]大的数
low = 1 ;
high = stk[0] ;
while ( low <= high ) {
mid = (low-high)/2+high ;
if ( stk[mid] < signals[i] ) {
low = mid+1 ;
}
else {
high = mid-1 ;
}
}
if ( low == stk[0]+1 ) ++stk[0] ;
stk[low] = signals[i] ;
}
printf ( "%d\n" , stk[0] ) ;
}
//system("pause") ;
return 0 ;
}