这是一篇由超级菜的OIer写的博客……
image

LIS就是最长上升子序列,通过DP求解。
普通的求法是\(O({n}^{2})\)的(相信大家都会写):

解法1

#include <bits/stdc++.h>
using namespace std;
const int N = 105, inf = 1e9 + 10;
int a[N], f[N];
int n, ans = -inf;
int main() {
	cin >> n;
	for (int i = 1; i <= n; i ++) {cin >> a[i]; f[i] = 1;}
	for (int i = 1; i <= n; i ++)
		for (int j = 1; j < i; j ++)
			if (a[i] > a[j]) f[i] = max(f[i], f[j] + 1);
	for (int i = 1; i <= n; i ++)
		ans = max(ans, f[i]);
	cout << ans << '\n';
	return 0;
}

n如果比较大,这种做法当然就不行了。
有一种基于贪心的二分求解方法,我们维护一个最长上升序列,扫描a[i], a[i]>序列末尾的数,加进去,序列长度+1;如果a[i] <=序列末尾,我们可以通过二分找到序列中第一个大等于a[i]的数,用a[i]替换掉它(相当于在不改变序列单调性的前提下使某一个数减小),这就是基于贪心思想的求法,最后序列的长度也就是我们要找的LIS。

解法2

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, inf = 1e9 + 10;
int f[N], a[N];
int n, ans;
int main() {
	cin >> n;
	for (int i = 1; i <= n; i ++) cin >> a[i], f[i] = inf;
	f[1] = a[1];
	ans = 1;//初始答案
	for (int i = 2; i <= n; i ++) {
		if (a[i] > f[ans]) f[++ ans] = a[i];
		else f[lower_bound(f + 1, f + ans + 1, a[i]) - f] = a[i];
	}
	cout << ans << '\n'; 
}

这样复杂度就优化成\(O(nlogn)\)了。

还有没有其他做法呢?众所周知,算法的魅力有很大一部分在于寻找不断优化的过程,我们进一步分析DP过程,f[i] = max(f[j] + 1, f[i]) (a[j] < a[i], j < i),而我们每次更新f[i]时,都要枚举1~i-1的所有j,这是很大的浪费。我们可以用树状数组优化,
对于每个a[i],查询它前面最大的f[j],f[i] = f[j] + 1,再向树状数组a[i]位置中插入f[i],该树状数组以值域为下标,维护的是最大的f[j]。复杂度同样是\(O(nlogn)\), 如果数值较大,记得离散化。
image

解法3

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, c[N], f[N], a[N];
void add(int x, int y) {
	while (x < N) {
		c[x] = max(c[x], y);
		x += x & (-x);
	}
}
int query(int x) {
	int res = 0;
	while(x) {
		res = max(res, c[x]);
		x -= x & (-x);
	}
	return res;
}
signed main() {
	cin >> n;
	for (int i = 1; i <= n; i ++) cin >> a[i];
	for (int i = 1; i <= n; i ++) {
		f[i] = query(a[i]) + 1;
		add(a[i], f[i]);
	}
	int ans = 0;
	for (int i = 1; i <= n; i ++) ans = max(ans, f[i]);
	cout << ans << '\n';
}

LCS最长公共子序列。给你两个序列,求他们的最长公共部分。

常规解法:

#include <bits/stdc++.h>
using namespace std;
const int N = 5010;
int a[N], b[N], f[N][N], n, m;
int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i ++) cin >> a[i];
	for (int i = 1; i <= m; i ++) cin >> b[i];
	for (int i = 1; i <= n; i ++) {
		for (int j = 1; j <= m; j ++) {
			f[i][j] = max(f[i - 1][j], f[i][j - 1]);
			if (a[i] == b[j]) f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1);
		}
	}
	cout << f[n][m];
}

n较大时这种解法肯定不能用了,此时题目应该还会给其他条件。
看一道题:
P1439 【模板】最长公共子序列
两个序列是相同长度n,且其中的元素范围都是1~n,观察n可以取到\(10 ^ 5\),那么上面的方法肯定不能用,我们可以将a序列每个元素映射为编号,这样a序列就是1~n,对于b采用a的映射方式,问题就转化为了求b的最长上升子序列了,采用二分的方式。
image

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, inf = 1e9 + 10;
int n, a[N], b[N], mp[N], f[N];
int main() {
	cin >> n;
	for (int i = 1; i <= n; i ++) {cin >> a[i]; mp[a[i]] = i;}
	for (int i = 1; i <= n; i ++) {cin >> b[i]; f[i] = inf;}
	int len = 0;
	for (int i = 1; i <= n; i ++) {
		if (mp[b[i]] > f[len]) f[++ len] = mp[b[i]];
		else f[lower_bound(f + 1, f + len + 1, mp[b[i]]) - f] = mp[b[i]];
		//找f数组中第一个>=mp[b[i]]的位置。 
	}
	cout << len << '\n';
	return 0;
}

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性