LIS问题
LIS
标签(空格分隔): 学术
图片来自@梦语小猪头的博客,如有侵权,请联系删除。
LIS,最长上升子序列
DP的入门题,裸的算法复杂度 \(O(n_2)\) ,代码如下
#include <bits/stdc++.h>
using namespace std ;
const int maxn = 1e6 + 50 ;
int n , m , ans ;
int a[maxn] , f[maxn] ;
signed main()
{
cin >> n ;
for(int i = 1 ; i <= n ; i++ )
cin >> a[i] , f[i] = 1 ;
f[0] = 0 ;
for(int i = 1 ; i <= n ; i++ )
for(int j = 0 ; j < i ; j++ )
if(a[j] < a[i])
f[i] = max(f[i] , f[j] + 1 ) ;
for(int i = 1 ; i <= n ; i++ )
ans = max(ans , f[i]) ;
cout << ans ;
return 0 ;
}
嗯……这 \(n_2\) 的思路我就不仔细讲解了 , 简单过一遍 dp 数组 \(f_i\) 表示到 \(i\) 位置的最长上升子序列的长度 , \(f_i = \max (f_j + 1)\) ,显然 \(i\) 点可以被它前面比它优的点更新 。
考虑优化
现在观察下我们的算法复杂度的瓶颈在哪?
显然我们在暴力地枚举 \(i\) 前的点,每一遍的复杂度是 \(O(n)\) 的,想要突破这个瓶颈要考虑如何将这暴力优化的更优美些……
优化方法——二分法
我们可以用二分查找的方式优雅地将枚举 \(O(n)\) 的复杂度将为 \(O(log_n)\) 。
实现方法
这里我们需要定义一个数组 \(C_i\) (\(i \in 1\) ~ \(n\)) , 代表 \(LIS\) 长度为 \(i\) 时 , \(LIS\) 中最小的元素。

根据例子,不难发现 \(C\) 是一个单调不降的序列,同时也是一组最长不下降子序列的解,所以可以用二分查找的方式,在 \(log_n\) 的复杂度求出
f[1] = 1 ; cnt[1] = b[1] ;
for(int i = 2 ; i <= n ; i++ )
{
int u = upper_bound(cnt+1 , cnt + n + 1 , b[i]) - cnt ;
f[i] = u ;
cnt[f[i]] = min(cnt[f[i]] , b[i]) ;
}
for(int i = 1 ; i <= n ; i++)
ans = max(ans , f[i]) ;
代码如上 。
例题luogu
做法,由于数据保证 \(P_1\) \(P_2\) 出现元素相同,且没有重复元素。所以将 \(P_i\) 以元素的值为下标,储存 \(P_i\) 的 \(i\)。 \(id_i\)在输入第二个序列的时候,\(b_i\) = \(id[cin>>x]\) 此时的 \(b_i\) 表示当前元素在 \(P_i\) 中出现的位置。
由于只是修改了序列的排列方式,每个元素的对应关系并没有改变,所以此时的 \(b_i\) 和 \(id_i\) 的最长公共子序列就是答案。现在观察 \(id_i\) 和 \(b_i\)。会发现现在的 \(id_i\) 是严格递增的,所以求解两序列 \(LCS\) 就等价于求解 \(b\) 序列的 \(LIS\)。
参考代码
for(int i = 1 ; i <= n ; i++ )
{
cin >> a[i] ;
id[a[i]] = i ;
}
for(int i = 1 ; i <= n ; i++ )
{
int x ; cin >> x ;
b[i] = id[x] ;
//f[i] = 1 ;
}

浙公网安备 33010602011771号