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 ;
	}
posted @ 2022-02-10 08:06  Simon_...sun  阅读(55)  评论(0)    收藏  举报