UVA 10534 - Wavio Sequence

这道题的意思是让我们求一个上升子序列  + 一个下降字序列,且两边的长度是相等的,由于用正常的 O(n2) 算法会 TLE ,所以这里我们采用二分法求最长上升子序列,这里需要利用两个栈来储存“相当于”最长上升子序列的串(我利用一个栈通过再次初始化栈顶top来第二次使用),在此同时开两个数组f1[i],f2[i],分别表示前i个元素的最长字串长度,最后比较同一个位置时(注意f1,f2一个正一个反)两个数组值的大小,取小的一个(因为要求两边长度相等)赋值给新开的数组flag[](我用num不断刷新),最后遍历数组,找出其最大的就可以了。

这里说一下二分法求最长上升子序列:复杂度为O(n×logn):

它是通过一个栈来实现的,我们遍历一个母串,如果当前值大于栈顶元素的值,我们将其压入栈,而当前位置 i 的最长上升子序列的长度就是栈顶指针的值(或+1),如果当前值等于栈顶元素的值,不压入栈,同样当前位置 i 的最长上升子序列的值就是栈顶指针的值(或+1),如果当前值小于栈顶元素的值,不压入栈,但是我们要用二分法找出恰好不小于当前值的那个位置,这个位置我们这里定义为x,并将x位置的值替换为当前值,而当前位置 i 的最长上升子序列的长度就是x这个指针的值(或+1);

为什么这样是成立的呢:别人的证明:

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

    简单证明一下其正确性。我们用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>
#define MAXN 10010
int a[MAXN], f[MAXN], f1[MAXN], f2[MAXN];
int n,num;
int up_bd(int *f, int t, int v)
{
int m;
int first = 0;
while(first < t)
{
m = first + (t - first)/2;
if(f[m] < v) first = m + 1;
else t = m;
}
f[first] = v;
return first;
}
void solve()
{
int top;
top = 0;
f[0] = a[0];
f1[0] = 1;
int x,y;
for(int i = 1; i < n; i ++)
{
if(a[i] > f[top]) {f[++top] = a[i];x = top + 1;}
else if(a[i] == f[top]) {f[top] = a[i];x = top + 1;}
else if(a[i] < f[top]) x = up_bd(f,top,a[i])+1;
f1[i] = x;
}
top = 0;
f[0] = a[n-1];
f2[0] = 1;
for(int i = n-2; i >= 0; i --)
{
if(a[i] > f[top]) {f[++top] = a[i];y = top + 1;}
else if(a[i] == f[top]) {f[top] = a[i];y = top + 1;}
else if(a[i] < f[top]) y = up_bd(f,top,a[i])+1;
f2[n-1-i] = y;
}
num = 0;
int min = 0;
for(int i = 0; i < n; i ++)
{
if(f1[i] > f2[n-i-1]) min = f2[n-i-1];
else min = f1[i];
if(min > num) num = min;
}
printf("%d\n",2*num - 1);
}
void input()
{
while(scanf("%d",&n) == 1)
{
for(int i = 0; i < n; i ++)
scanf("%d",&a[i]);
solve();
}
}
int main()
{
input();
return 0;
}




posted on 2012-03-31 22:35  BFP  阅读(779)  评论(0编辑  收藏  举报