最长上升子序列c语言二分,动态规划之最长上升子序列模型

本文详细介绍了最长上升子序列问题的动态规划解决方案,包括O(N^2)和O(NlogN)求解方法,并重点讲解了如何通过树状数组优化dp数组,提升效率。涉及的知识点包括正反双向LIS、最大上升子序列和Dilworth定理。实例涵盖怪盗基德、登山、合唱队形和友好城市问题,展示了如何灵活应用这些算法解决实际问题。
摘要由CSDN通过智能技术生成

动态规划分为很多模型,比如说数字三角形模型,最长上升子序列模型,背包模型,状态机模型,状态压缩,区间dp,树形dp等等

下面,我就Acwing提高课中,最长上升子序列模型进行了整理。

涉及知识点总览

主要涉及的算法知识点有:

最长上升子序列 O

(

N

2

)

O(N^2)O(N2)和O

(

N

l

o

g

N

)

O(NlogN)O(NlogN)求解方法

正反双向 LIS

最大上升子序列和

偏序集-Dilworth定理的两种证明

这里补充一个 O

(

N

2

)

O(N^2)O(N2) dp 数组的优化

f[i] 代表的是 以 i 为结尾的最长上升子序列的长度,他需要 f[i-1] …f[i-2] … 的信息来更新自己

参考网上思路,发现可以用树状数组。用数值做下标,维护长度最大值,从后往前循环,每次查询之前已经放到树状数组里面的数中以这个数结尾的最长不上升子序列的长度的最大值,然后把这个最大值+1作为以自己结尾的最长不上升子序列的长度,放到树状数组里面

这里我们简单温习一下树状数组:

一、怪盗基德的滑翔翼

题目链接如下怪盗基德的滑翔翼

8e773642602847ac8b508b2621b4c55f.jpg

根据题目的描述信息可知,该题目就是要求对于某一个建筑,往 左 能够最长下降子序列的长度,或者是往 右的租场下降子序列的长度,因此有两种解法一种是我们常见的dp最长上升子序列,还有一种就是贪心最长上升子序列。

下面,是我给出的两种解法,具体的解释都放在了代码里面

#include

using namespace std;

const int N = 110;

int f[N], a[N], g[N];

int ans, n;

/*

dp LIS

f[i] = f[j] + 1 (j < i && a[j] < a[i])

正方跑两次

*/

void sol1() {

int ans = 0;

for (int i = 1; i <= n; i ++ ) {

f[i] = 1;

for (int j = 1; j < i; j ++ ) {

if (a[j] < a[i]) {

f[i] = max(f[i], f[j] + 1);

}

}

ans = max(ans, f[i]);

}

for (int i = n; i >= 1; i -- ) {

g[i] = 1;

for (int j = n; j > i; j -- ) {

if (a[j] < a[i]) {

g[i] = max(g[i], g[j] + 1);

}

}

ans = max(ans, g[i]);

}

printf("%d\\n", ans);

}

/*

使用最长上升子序列模型的贪心解法

f[i] 表示长度为 i 的最小末尾

不难得知,f[i] 数组应该是单调递增的(相等的情况都不会出现)

对于每次循环 数组 a 的元素,都是将 寻找小于 a[i] 的最大 f[j] 的位置,然后更新f[j + 1]

初始化数组memset(f, 0x3f, sizeof f), f[0] = 0;

*/

void sol2() {

int len = 1, ans = 0;

memset(f, 0x3f, sizeof f);

f[0] = 0;

f[1] = a[1];

for (int i = 2; i <= n; i ++ ) {

static int l, r, mid;

l = 0, r = len;

while (l < r) {

mid = l + r + 1 >> 1; // 注意这里的 + 1

if (f[mid] < a[i]) {

l = mid;

} else {

r = mid - 1;

}

}

f[l + 1] = a[i];

len = max(len, l + 1); // 更新 len

}

ans = max(ans, len);

len = 1;

memset(f, 0x3f, sizeof f);

f[0] = 0;

f[1] = a[n];

for (int i = n - 1; i >= 1; i -- ) {

static int l, r, mid;

l = 0, r = len;

while (l < r) {

mid = l + r + 1 >> 1; // 注意这里的 + 1

if (f[mid] < a[i]) {

l = mid;

} else {

r = mid - 1;

}

}

f[l + 1] = a[i];

len = max(len, l + 1);

}

ans = max(ans, len);

printf("%d\\n", ans);

}

int main()

{

int T; cin >> T;

while (T -- ) {

scanf("%d", &n);

for (int i = 1; i <= n; i ++ ) {

scanf("%d", &a[i]);

}

// sol1();

sol2();

}

return 0;

}

二、登山

Acwing 登山题目链接

08839adf90ec470e9c964aa63e57fb23.jpg

该登山题目和原本的 怪盗基德 问题有些类似,但是又有些不同,他们都需要正反两次求出最长上升子序列,但是不同的是怪盗基德可以使用 nlog(n) 的贪心算法,因为他不必在意于最后的结尾是哪个点。

但是,登山问题是需要知道 该点的位置是在哪里的。

因此登山问题必须要使用 O

(

N

2

)

O(N^2)O(N2)的dp求解,

f[i] 表示以i 为结尾的最长上升子序列

#include

using namespace std;

const int N = 1010;

int f[N], a[N], g[N];

int ans, n;

/*

dp LIS

f[i] = f[j] + 1 (j < i && a[j] < a[i])

正方跑两次

*/

void sol1() {

int ans = 0;

for (int i = 1; i <= n; i ++ ) {

f[i] = 1;

for (int j = 1; j < i; j ++ ) {

if (a[j] < a[i]) {

f[i] = max(f[i], f[j] + 1);

}

}

}

for (int i = n; i >= 1; i -- ) {

g[i] = 1;

for (int j = n; j > i; j -- ) {

if (a[j] < a[i]) {

g[i] = max(g[i], g[j] + 1);

}

}

}

for (int i = 1; i <= n; i ++ ) {

ans = max(ans, f[i] + g[i] - 1); // 注意这里的 - 1

}

printf("%d\\n", ans);

}

int main()

{

int T; T = 1;

while (T -- ) {

scanf("%d", &n);

for (int i = 1; i <= n; i ++ ) {

scanf("%d", &a[i]);

}

sol1();

}

return 0;

}

三、合唱队形

Acwing 合唱队型

9f53e0f4cc044afe8df90d0b78d0a172.jpg

本题就是换了一种问法,最少有多少同学出列,就是需要求出我们的最长上升子序列,和我们的登山问题是一个性质,也是只可以使用O

(

N

2

)

O(N^2)O(N2)算法进行求解

#include

using namespace std;

const int N = 1010;

int f[N], a[N], g[N];

int ans, n;

/*

dp LIS

f[i] = f[j] + 1 (j < i && a[j] < a[i])

正方跑两次

*/

void sol1() {

int ans = 0;

for (int i = 1; i <= n; i ++ ) {

f[i] = 1;

for (int j = 1; j < i; j ++ ) {

if (a[j] < a[i]) {

f[i] = max(f[i], f[j] + 1);

}

}

}

for (int i = n; i >= 1; i -- ) {

g[i] = 1;

for (int j = n; j > i; j -- ) {

if (a[j] < a[i]) {

g[i] = max(g[i], g[j] + 1);

}

}

}

for (int i = 1; i <= n; i ++ ) {

ans = max(ans, f[i] + g[i] - 1); // 注意这里的 - 1

}

printf("%d\\n", n - ans);

}

int main()

{

int T; T = 1;

while (T -- ) {

scanf("%d", &n);

for (int i = 1; i <= n; i ++ ) {

scanf("%d", &a[i]);

}

sol1();

}

return 0;

}

四、友好城市

Acwing 友好城市链接

78e7c3d1fa544326b0076e65045de640.jpg

对于本题而言,他所求的不相交的航线,就是我们排序之后的不递减子序列!

排序,可以按照是左岸排序,也可以按照右岸进行排序

/*

这个题目个关键在于,你连一连这个友好城市的航线,你就会发现

航线不相交,就是排序之后他们的那个最长上升子序列

*/

#include

using namespace std;

const int N = 5010;

int n, f[N], len;

class Node {

public:

int x, y;

Node() {

//

}

Node(int x, int y): x(x), y(y) {

//

}

}a[N];

bool cmp1(const Node &t1, const Node &t2) {

if (t1.x == t2.x) {

return t1.y <= t2.y;

} else {

return t1.x < t2.x;

}

}

bool cmp2(const Node &t1, const Node &t2) {

if (t1.y == t2.y) {

return t1.x <= t2.x;

} else {

return t1.y < t2.y;

}

}

int sol1() {

sort(a + 1, a + n + 1, cmp1);

// sort(a + 1, a + n + 1, cmp2);

// 求解的是最长非递减子序列

/*

因此变成了寻找 f[i] 中 小于等于 val 的最大位置

*/

int val;

memset(f, 0x3f, sizeof f);

f[0] = 0;

len = 0;

for (int i = 1; i <= n; i ++ ) {

val = a[i].y;

static int l, r, mid;

l = 0, r = len;

while (l < r) {

mid = l + r + 1 >> 1;

if (f[mid] <= val) { // 注意这里是 小于等于

l = mid;

} else {

r = mid - 1;

}

}

f[l + 1] = val;

len = max(len, l + 1);

}

cout << len << endl;

}

int sol2() {

sort(a + 1, a + n + 1, cmp2);

// 求解的是最长非递减子序列

/*

因此变成了寻找 f[i] 中 小于等于 val 的最大位置

*/

int val;

memset(f, 0x3f, sizeof f);

f[0] = 0;

len = 0;

for (int i = 1; i <= n; i ++ ) {

val = a[i].x;

static int l, r, mid;

l = 0, r = len;

while (l < r) {

mid = l + r + 1 >> 1;

if (f[mid] <= val) { // 注意这里是 小于等于

l = mid;

} else {

r = mid - 1;

}

}

f[l + 1] = val;

len = max(len, l + 1);

}

cout << len << endl;

}

int main()

{

cin >> n;

for (int i = 1; i <= n; i ++ ) {

scanf("%d%d", &a[i].x, &a[i].y);

}

//sol1();

sol2();

return 0;

}

五、最大上升子序列和

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值