首页 > 代码库 > LIS教学篇

LIS教学篇

LIS是最长上升子序列,(递增子序列是指,子序列的元素是递增的)例如:5 1 6 8 2 4 5 10,最长递增子序列是1 2 4 5 10。

对于LIS,有两种解法,一种是比较容易想到的O(n^2)的解法,我们用N[i]表示第i个数,dp[i]表示第i个数所在子序列中最大的长度(比如对于序列{1,2,5,4,2},那dp={1,2,3,3,2}).那么我们只要枚举j从1到i-1,且N[j]<N[i],就可以进行更新:dp[i]=min(dp[i],dp[j]+1),这样的复杂度是O(n^2)。

O(n^2)的复杂度往往是不如人意的,我们可以考虑尝试优化,如果我们用dp[i]表示长度为i的子序列的最后一个数的最小值,那么我们可以发现这样的dp数组一定是非递减的,因为如果长度为2的子序列末尾最小值是3,那长度为1的子序列末尾最小值肯定可以取3,是不可能小于3的,因此我们可以先把数组初始化为INF,每次更新的时候二分一下dp数组,找到一个大于等于N[i]的数的位置,然后在这个地方更新。举个例子,对于

5 1 6 8 2 4 5 10这个序列

一开始dp数组为:

INF INF INF INF INF INF INF INF

更新5的时候,在1的位置(下标从1开始)更新,表示长度为1的子序列的最后一个数最小是5,此时dp数组为

5 INF INF INF INF INF INF INF

更新1的时候,二分到1的位置,在1的位置更新,表示长度为1的子序列的最后一个数最小是1,此时dp数组为

1 INF INF INF INF INF INF INF

更新6的时候,二分到2的位置,在2的位置更新,表示长度为2的子序列的最后一个数最小是6,此时dp数组为

1 6 INF INF INF INF INF INF

更新8的时候,二分到3的位置,在3的位置更新,表示长度为3的子序列的最后一个数最小是8,此时dp数组为

1 6 8 INF INF INF INF INF

更新2的时候,二分到2的位置,在2的位置更新,表示长度为2的子序列的最后一个数最小是2,此时dp数组为

1 2 8 INF INF INF INF INF

更新4的时候,二分到3的位置,在3的位置更新,表示长度为3的子序列的最后一个数最小是4,此时dp数组为

1 2 4 INF INF INF INF INF

更新5的时候,二分到4的位置,在4的位置更新,表示长度为4的子序列的最后一个数最小是5,此时dp数组为

1 2 4 5 INF INF INF INF

更新10的时候,二分到5的位置,在5的位置更新,表示长度为5的子序列的最后一个数最小是10,此时dp数组为

1 2 4 5 10 INF INF INF

这时候dp数组处理完毕,5为LIS长度。

可以在这里测试下代码:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1134

代码如下:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <queue>
#include <string>
#include <stack>
#include <map>
#include <set>
#include <bitset>
#define X first
#define Y second
#define clr(u,v); memset(u,v,sizeof(u));
#define in() freopen("data","r",stdin);
#define out() freopen("ans","w",stdout);
#define Clear(Q); while (!Q.empty()) Q.pop();
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e5 + 10;
const int INF = 0x3f3f3f3f;
int c[maxn];//c[i]代表序列长度为i时的最小数字
int main()
{
    int n, x;
    scanf("%d", &n);
    clr(c, 0x3f);//一开始全部初始化为INF
    for (int i = 0; i < n; i++)
    {
        scanf("%d", &x);
        int pos = lower_bound(c + 1, c + 1 + n, x) - c;//找到c中第一个大于等于x的数字的下标
//        for (int j = 1; j <= n; j++)
//            printf("%d ", c[j]);
//        puts("");
//        printf("pos : %d\n", pos);
        c[pos] = min(c[pos], x);
    }
    printf("%d\n", lower_bound(c + 1, c + 1 + n, INF) - (c + 1));
    return 0;
}

 

LIS教学篇