lower_bound 优化的最长上升子序列 (时间复杂度n*logn)

最长上升子序列一般是用动规来进行求解,这里要说的是运用lower_bound进行优化的最长上升子序列,又快又好写。

先说lower_bound

lower_bound可以找出数组中>=某个数的第一个值,并返回其地址。
比如我们找f数组中>=a[i]的第一个数,当前f数组为1、2、7、9。若a[i]=6,令pos=lower_bound(f+1,f+1+n,a[i])-f,则返回的值pos=3,因为第三个数就是7,>6;

但是有一点一定要注意,那就是执行lower_bound的数组一定要是有序的,因为lower_bound其实就是系统自带的二分查找。

关于lower_bound求最大上升子序列的方法我们由一道题来引入:

2.独立集
(bubble.pas/c/cpp)
【问题描述】
有一天,一个名叫顺旺基的程序员从石头里诞生了。又有一天,他学会了冒泡排序和独
立集。在一个图里,独立集就是一个点集,满足任意两个点之间没有边。于是他就想把这两
个东西结合在一起。众所周知,独立集是需要一个图的。那么顺旺基同学创造了一个算法,
从冒泡排序中产生一个无向图。
这个算法不标准的伪代码如下:
void bubblesortgraph(n,a[])
//输入:点数n,1到n的全排列a
//输出:一个点数为n的无向图G
{ 创建一个有n个点,0条边的无向图G。
do{ swapped=false
for i 从1 到n-1
if(a[i]>a[i+1])
{ 在G中连接点a[i]和点a[i+1]
交换a[i]和a[i+1]
swapped =true
}
}while(swapped);
输出图G。
}
//结束。

那么我们要算出这个无向图G最大独立集的大小。但是事情不止于此。顺旺基同学有时
候心情会不爽,这个时候他就会要求你再回答多一个问题:最大独立集可能不是唯一的,但
有些点是一定要选的,问哪些点一定会在最大独立集里。今天恰好他不爽,被他问到的同学
就求助于你了。
【输入】
输入包含两行,第一行为N,第二行为1 到N 的一个全排列。
【输出】
输出包含两行,第一行输出最大独立集的大小,第二行从小到大输出一定在最大独立集
的点的编号。
【输入输出样例】
bubble.in bubble.out
3
3 1 2
2
2 3
【样例说明】
如上图,顶点1和2一定在最大独立集中,其对应的编号为2和3。
【数据范围】
30%的数据满足N<=16
60%的数据满足N<=1,000
100%的数据满足N<=100,000

这道题从伪代码来看,明显是逆序对,也就是说一个数一定会和在它右边且比其小的数连一条边,且要求最后求出来的独立点集中两两之间没有边,其实也就是说不存在边。
那么问题到这里也就有些显然了,只要我们维持找出来的这些点的大小是上升的,就一定不会有边(因为每个数的左边都不存在比它小的数)。
那么第一题的解就是最长上升子序列的长度。
但是第二题应该怎么求解呢?
它要我们找出一定存在于所有最长上升子序列的点,但是我们不可能枚举出所有的最长上升子序列。
其实这里只用保证我们需要找出的点是唯一的,也就是不能够被替代的。

#include
#include
#include
using namespace std;
const int maxn=3000005;
int num[maxn],f[maxn],_left[maxn],_right[maxn],cnt[maxn];
int n;
int maxx=0,pos;

int main()
{
    freopen("bubble.in","r",stdin);
    freopen("bubble.out","w",stdout);
    int i,j,k;
    scanf("%d",&n);
    for(i=1;i<=n;i++) scanf("%d",&num[i]);

    for(i=1;i<=n;i++)
    {
        pos=lower_bound(f+1,f+maxx+1,num[i])-f;
        maxx=max(maxx,pos);
        _left[i]=pos;
        f[pos]=num[i];
    }

    f[0]=-999999999,maxx=0;
    for(i=n;i;i--)
    { 
        pos=lower_bound(f+1,f+maxx+1,-num[i])-f;
        maxx=max(maxx,pos);
        _right[i]=pos;
        f[pos]=-num[i];
    }

    printf("%d\n",maxx);
    for(i=1;i<=n;i++)
    if(_left[i]+_right[i]==maxx+1) cnt[_left[i]]++;
    for(i=1;i<=n;i++)
    if(cnt[_left[i]]==1&&_left[i]+_right[i]==maxx+1) printf("%d ",i); 
    return 0;
}

可以看出,其中的精华就是

for(i=1;i<=n;i++)
    {
        pos=lower_bound(f+1,f+maxx+1,num[i])-f;
        maxx=max(maxx,pos);
        _left[i]=pos;
        f[pos]=num[i];
    }

其中的f[]数组记录的就是我们求得的最长上升子序列中的元素,maxx记录的是最大上升子序列的长度,因为后面我们把f[pos]改为了num[i],其实就是用num[i]替换了第一个比它大的f[pos],因为这样可以让数列尽量的长,有一点贪心原则。所以_left[i]记录的就是以num[i]结尾的最长上升子序列的长度,便于求解第二问。

我们用一组数据模拟一下:
5 8 9 2 3 1 7 4 6
f[i]
1 : 5->2->1
2 : 8->3
3 : 9->7->4
4 : 6
5 :
( -> 表示替换)

从中可以看出,我们确实求出了最长上升子序列,并且_left[i]的正确性也得以证实。

你可能感兴趣的:(STL)