UVA_10534

    这个题目需要用到nlogn求最长上升子序列的算法。

    算法的思想大概是这个样子,维护一个栈,在扫描过程中,如果当前元素比栈顶元素大,那么就将其加入到栈顶,否则在栈内用二分查找找到恰好不小于当前元素的某一值,并将其替换掉。当前元素为终点的最长上升子序列的长度就是该元素入栈时的位置。

    实际上就是运用了贪心的思想,每次进行替换操作后增强了后续最长上升子序列的“长度增长的潜力”,后续得到的解一定不会比不替换更优。

    简单证明一下其正确性。我们用a[i]替换掉了S[mid],对后续某一元素a[j]来讲,可能会认为此时a[j]前的元素标号并不是严格升序了,这会不会有问题呢?实际是不会有问题的。我们不妨设a[j]左边的元素依次为A1、A2、……,这样A1我们必然可以不动,因为此前A1已经更新至最新了,标号必然是所有曾在这个位置的元素中的标号最大的一个,那么对于A2呢,我们不妨想想现在的A1刚进入这个栈的时候吧,那个时候的A2必然也在那个时候之前更新到当时的最新了吧,并且A1左边的元素的个数从那个时刻起也没有变过吧?现在这个问题就可以不断递归下去了,我们按这种方式一定可以找到一个序号严格递增的序列,即使这个序列并不是在a[j]入栈时我们所看到的序列,但这又有什么关系呢,反正结果对就是了。

    再简单说明一下其最优性。既然前面已经证明了其正确性,那么按前面的方法至少可以得到一个长度可观的最长上升子序列,但究竟其是否是最长的呢?按上面算法的思路,如果想要得到的长度更长的话,那么只有一个措施,就是将原来的替换操作变成插入操作,要不然是没办法增加长度的。然而如果将替换操作变成插入操作,我们还能保证一定可以得到一个标号严格升序的上升子序列吗?显然不能。比如一次插入操作在S[k]和S[k+1]之间,我们插入了一个a[i],但是如果此时有a[j]>S[k+1],那么a[j]的左边是没办法构造出一个长度为k+2的标号严格升序的上升子序列的。既然第一次插入操作都会有错,那肯定就不用考虑后续再有插入操作了。

#include<stdio.h>
#include<string.h>
#define MAXD 10010
int N, a[MAXD], f1[MAXD], f2[MAXD], s[MAXD];
void init()
{
int i;
for(i = 0; i < N; i ++)
scanf("%d", &a[i]);
}
int Min(int x, int y)
{
return x < y ? x : y;
}
void solve()
{
int i, j, k, top, min, mid, max;
s[top = 0] = -1;
for(i = 0; i < N; i ++)
{
if(a[i] > s[top])
{
s[++ top] = a[i];
f1[i] = top;
}
else
{
max = top;
min = 0;
for(;;)
{
mid = (max + min + 1) / 2;
if(mid == max)
break;
if(s[mid] < a[i])
min = mid;
else
max = mid;
}
s[mid] = a[i];
f1[i] = mid;
}
}
s[top = 0] = -1;
for(i = N - 1; i >= 0; i --)
{
if(a[i] > s[top])
{
s[++ top] = a[i];
f2[i] = top;
}
else
{
max = top;
min = 0;
for(;;)
{
mid = (max + min + 1) / 2;
if(mid == max)
break;
if(s[mid] < a[i])
min = mid;
else
max = mid;
}
s[mid] = a[i];
f2[i] = mid;
}
}
max = 0;
for(i = 0; i < N; i ++)
{
k = 2 * Min(f1[i], f2[i]) - 1;
if(k > max)
max = k;
}
printf("%d\n", max);
}
int main()
{
while(scanf("%d", &N) == 1)
{
init();
solve();
}
return 0;
}


posted on 2011-11-15 01:17  Staginner  阅读(436)  评论(0编辑  收藏  举报