最长公共子序列(LIS)

题目描述

时间限制:1.0s 内存限制:256.0MB

问题描述

   给定一个长为\(n\)的序列,求它的最长上升子序列的长度。

输入格式

   输入第一行包含一个整数\(n\)
   第二行包含\(n\)个整数\(a_1,a_2,…,a_n\),为给定的序列。

输出格式

   输出一个非负整数,表示最长上升子序列的长度。

样例输入

 5
 1 3 2 5 4

样例输出

 3

数据规模和约定

   \(0<n\leq10^5\),每个数不超过\(10^6\)

解析

   所谓最长上升子序列,就是给定一列数,求序列中严格上升的子序列,子序列中数的位置不一定连续。

1.动态规划dp

   这是动态规划中的一个经典应用题目,动态规划的思想就是把问题分解为一些本质上还是相同问题的小问题,这些小问题的区别仅在于输入的参数不同,而每一个小问题的解都可以由比他参数更小的一些小问题的解推出,最小的小问题有显而易见的解,这被称之为边界。最长上升子序列(LIS)问题也是这样。
   \(a_i\)\(1\leq i<n\))表示序列中第\(i\)个整数
   \(dp_i\) 表示以\(a_i\)为结尾的最长上升子序列的长度
   于是,\(dp_i\)要把\(a_i\)加到以\(a_j\)\(1\leq j<i\))为上升子序列末尾的后面,并且符合\(a_j<a_i\),找一个最大的dp[j],然后令\(dp_i = dp_j + 1\)即求出了\(dp_i\)
   最后在所有的\(dp_i\)中取一个最大的即为整个序列的最长上升子序列的长度。
   算法时间复杂度为\(O(n^2)\),当范围大一些的时候是有些低效的。

#include <iostream>
#include <algorithm>
using namespace std;
int a[100005], d[100006];
const int INF = 0x3f3f3f3f;
int main() {
	int n; cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i];
	int maxx = -INF;
	for (int i = 1; i <= n; i++) {
		d[i] = 1;
		for (int j = 1; j < i; j++)
			if (a[j] < a[i])
				d[i] = max(d[i], d[j] + 1);
		maxx = max(maxx, d[i]);
	}
	cout << maxx << endl;
	return 0;
}
2.二分搜索优化

   \(a_i\)\(1\leq i<n\))表示序列中第\(i\)个整数
   \(dp_m\) 表示长度为\(m\)的最长公共子序列的末尾最小值
   当\(i=1\)时,\(dp_i=a_i\)
   当\(i\)逐渐增加,每次扫过\(a_i\),因为\(dp_m\)代表长度为i的最长上升子序列的最小末尾,这可以代表一个长度为m的最长上升子序列,我们考虑可以把\(a_i\)加到哪个最长上升子序列后面构成一个长度\(+1\)的新的最长上升子序列。那么我们就可以在\(dp\)数组中寻找到\(j\)\(1\leq j\leq m\))并且\(dp_j<a_i\leq dp_{j+1}\),把\(a_i\)加到这个数代表的序列后面:\(dp_{j+1}=a_i\)
   因为\(dp\)数组是有序的,所以在进行寻找的时候就可以用二分搜索来进行。而当我们把\(a\)数组全部扫描完之后,\(dp\)数组中的最后一个有效的数字的位置即是我们所求的最长上升子序列的长度\(m\)
   由于循环中的搜索变成了二分搜索,时间复杂度为\(O(log_n)\),所以整体时间复杂度是\(O(nlog_n)\)

#include <iostream>
#include <algorithm>
using namespace std;
int dp[100005], a[100005];
int b_search(int x, int l, int r) {
    while (l < r) {
        int mid = (r + l) / 2;
        if (dp[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return l;
}
int main(int argc, char** argv) {
    int n; cin >> n;
    for (int i = 1; i <= n; i++) {
    	cin >> a[i];
    	dp[i] = 0x3f3f3f3f;
    }
    int m = 1;
    dp[1] = a[1];
    for (int i = 2; i <= n; i++) {
        if (a[i] > dp[m]) dp[++m] = a[i];
        else {
        	int j = b_search(a[i], 1, m);
        	dp[j] = min(dp[j], a[i]);
        }
    }
    cout << m << endl;
    return 0;
}

   在第二个算法中我们可以通过在二分查找中改变“上确界”和“下确界”,以及在第一个算法中通过改变符号(“<”和“<=”或“>”、“>=”等),求出最长不下降、不上升、严格下降子序列等问题。

最长公共子序列(LIS)

上一篇:因out目录更新不同步,导致404 505 null问题


下一篇:滑动验证码(无原图片处理)