LIS的三种基本解法

· · 算法·理论

LIS的定义

一个长度为n序列a[]中最长的单调递增的子序列。

解法1:动态规划

**局限性:** 这里枚举$i$,还需要枚举$j$,所以时间复杂度为$O(n^2$),当$n$范围较大时不适用。

#### 解法2:贪心+二分
对于多组拥有同样长度的上升子序列,比如(2,3,7)和(2,3,4)两个子序列长度都是3。显然,4比7小,那么将来出现的数,更加有机会接在4的后面,即第二组在以后可能接上的数会更多。

- 设$f[len]$表示$LIS$长度为$len$时结尾元素的最小值。

- 显然,$f[]$数组是一个单调递增的数组。

维护$f[]$过程如下:设当前枚举到第$i$个数$a[i]$,$len$表示$f[]$当前长度。

- 若$a[i] > f[len]$, 则$ f[++len] = a[i]$;
- 否则,在$f[]$中找出第一个满足大于等于$a[i]$的数,替换掉$f[]$中的数,保持该长度时,数值尽可能小
```cpp
int main(){
    scanf("%d", &n);
    for(int i = 1; i<=n; i++)
        scanf("%d",a + i);

    for(int i = 1; i <= n; i++){
        if( a[i] > f[len] )
            f[++len] = a[i];
        else{
            int tmp = lower_bound(f + 1,f + len + 1, a[i]) - f; 
                f[tmp] = a[i];
        }
    }
    printf("%d\n", len);
    return 0;
}

算法时间复杂度为O(nlogn)

思考:若求的是最长不下降子序列,该如何修改? 比如这组数据:

10

3 2 2 2 2 2 3 2 2 2

for(int i = 1; i <= n; i++){
        if( a[i] >= f[len] ) //此处要把改为>=
            f[++len] = a[i];
        else{
            int tmp = upper_bound(f + 1,f + len + 1, a[i]) - f; //不是用lower_bound
                f[tmp] = a[i];
        }
    }

解法3:树状数组维护

回顾解法1的状态转移方程,修改方程如下:

先将$a[]$数组从小到大排序,同时将$a[]$排序之间的序号记录下来。然后从小到大枚举$a[]$,每次用编号小于等于a[i]的元素的长度+1来更新答案,同时把编号小于等于a[i]编号的元素的LIS长度+1。 ```cpp const int N = 1e5 + 10; int n, tr[N]; pair<int, int>a[N]; //first为权值,second为序号 void update(int x, int y){ while(x <= n){ tr[x] = max(tr[x], y); x += (x & (-x)); } } int query(int x){ int res = -1; while(x > 0){ res = max(res, tr[x]); x -= (x & (-x)); } return res; } int main(){ scanf("%d", &n); for(int i = 1; i <= n; i++){ scanf("%d", &a[i].first); a[i].second = i; } sort(a + 1, a + n + 1); int ans = 0; for(int i = 1; i <= n; i++){ if(i == 1 || a[i].first != a[i - 1].first) {//若没有这个判断,则求的是不下降子序列 int res = query(a[i].second);// 查找a[i].second内的最大值 ans = max(ans, res + 1); update(a[i].second, res + 1); } } printf("%d\n", ans); return 0; } ``` 练习:[LIS](https://www.luogu.com.cn/problem/AT_chokudai_S001_h)