最长上升子序列模型 1
LIS: Longest In 2. "倒数第二个数"到底是谁 -> 由谁转移而来
3.
easing Sequence

895 最长上升子序列
给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少
输入样例:
输出样例:
动态规划分析:
- 状态表示: f[i]
- 集合: 所有以 a[i] 结尾的 严格单调上升 子 2. "倒数第二个数"到底是谁 -> 由谁转移而来

- 属性: max / min / 数量
- 状态计算: 集合划分
- 划分依据: "最后一个不同的点"
- “倒数第二个数”到底是谁 -> 由谁转移而来

代码:
C++ |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 | #include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n;
int a[N], f[N];
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
for (int i = 1; i <= n; i ++ )
{
f[i] = 1; // 只有a[i]一个数
for (int j = 1; j < i; j ++ )
if (a[j] < a[i]) // 确保合法
f[i] = max(f[i], f[j] + 1);
}
int res = 0;
for (int i = 1; i <= n; i ++ ) res = max(res, f[i]);
printf("%d\n", res);
return 0;
}
|
1017 怪盗基德的滑翔翼
任选一个楼房 + 选定一个方向 + 只能向“下”跳:
最多可以经历多少个房?
分析:
如果确定了方向和起点, 最长的距离是什么?
- 左跳
- 起点: a[i]
- max距离: 以 a[i] 结尾的 最长上升子序列
C++ |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 | #include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int n;
int h[N];
int f[N];
int main()
{
int T;
scanf("%d", &T); // T 组测试数据
while (T -- )
{
scanf("%d", &n);
for (int i = 0; i < n; i ++ ) scanf("%d", &h[i]); // 输入楼房
int res = 0;
// 正向求解 LIS 问题
for (int i = 0; i < n; i ++ )
{
f[i] = 1;
for (int j = 0; j < i; j ++ )
if (h[i] < h[j])
f[i] = max(f[i], f[j] + 1);
res = max(res, f[i]);
}
memset(f, 0, sizeof f);
// 反向求解 LIS 问题
for (int i = n - 1; i >= 0; i -- )
{
f[i] = 1;
for (int j = n - 1; j > i; j -- )
if (h[i] < h[j])
f[i] = max(f[i], f[j] + 1);
res = max(res, f[i]);
}
printf("%d\n", res);
}
return 0;
}
|
1014 登山
- 条件1: 编号递增 顺序浏览 -> 必须是子序列
- 条件2: 相邻两个景点不能相同 -> 严格单调
- 条件3: 一旦开始下降, 就不能再上升 -> 严格单调上升 + 严格单调下降
目标: 最多能浏览多少景点
- f[i]: 从左向右到 a[i]
- g[i]: 从右向左到 a[i]
- 答案: f[i] + g[i] -1
- 注意: 先预处理一遍f和g,减少复杂度
C++ |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 | #include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n;
int h[N];
int f[N], g[N];
int main()
{
scanf("%d", &n);
// 输入数据
for (int i = 0; i < n; i ++ ) scanf("%d", &h[i]);
// 预处理一遍: 正向最长单调上升子序列
for (int i = 0; i < n; i ++ )
{
f[i] = 1;
for (int j = 0; j < i; j ++ )
if (h[i] > h[j])
f[i] = max(f[i], f[j] + 1);
}
// 预处理一遍: 反向最长单调上升子序列
for (int i = n - 1; i >= 0; i -- )
{
g[i] = 1;
for (int j = n - 1; j > i; j -- )
if (h[i] > h[j])
g[i] = max(g[i], g[j] + 1);
}
int res = 0;
for (int i = 0; i < n; i ++ ) res = max(res, f[i] + g[i] - 1);
printf("%d\n", res);
return 0;
}
|
482 合唱队形
跟 1014 完全对偶
C++ |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41 | #include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int n;
int h[N];
int f[N], g[N];
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++ ) scanf("%d", &h[i]);
for (int i = 1; i <= n; i ++ )
{
f[i] = 1;
for (int j = 1; j < i; j ++ )
if (h[j] < h[i])
f[i] = max(f[i], f[j] + 1);
}
for (int i = n; i; i -- )
{
g[i] = 1;
for (int j = n; j > i; j -- )
if (h[j] < h[i])
g[i] = max(g[i], g[j] + 1);
}
int res = 0;
for (int i = 1; i <= n; i ++ ) res = max(res, f[i] + g[i] - 1);
// 跟 1014登山 完全对偶
printf("%d\n", n - res);
return 0;
}
|
1012 友好城市
条件:
- 每个城市只能建立一个桥
- 所有桥之间不能相交
- 建桥模式合法
思路: 一边排序,另一边“最长上升子序列”
C++ |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 | // 基于 “pair” 默认排序
#include <iostream>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 5010;
int n;
PII city[N];
int f[N];
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i ++ )
scanf("%d%d", &city[i].first, &city[i].second);
sort(city, city + n); // pair默认按照 "第一关键字" 排序
int res = 0;
for (int i = 0; i < n; i ++ )
{
f[i] = 1;
for (int j = 0; j < i; j ++ )
if (city[i].second > city[j].second)
f[i] = max(f[i], f[j] + 1);
res = max(res, f[i]);
}
printf("%d\n", res);
return 0;
}
|
C++ |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57 | // 基于 struct 手动指定排序 (recommended)
#include<bits/stdc++.h>
using namespace std;
const int N = 5010;
struct city_pair{
int city1;
int city2;
};
city_pair cities[N];
int n;
int f[N]; // 动态规划
bool cmp (city_pair a, city_pair b)
{
return a.city1 < b.city1;
}
int main()
{
cin >> n;
for (int i=1; i<=n; i++) {
cin >> cities[i].city1;
cin >> cities[i].city2;
}
sort(cities+1, cities+n+1, cmp);
// sort (起始地址, 结束地址, 比较函数)
// sort(a,b): [a, b). 左闭右开
// cmp: 回调函数. 可以自定义排序方式
for (int i=1; i<=n; i++)
{
f[i] = 1;
for (int j=1; j<i; j++)
{
if (cities[j].city2 < cities[i].city2)
{
f[i] = max(f[i], f[j] + 1);
}
}
}
int ans = 0;
for (int i=1; i<=n; i++)
{
ans = max(ans, f[i]);
}
cout << ans << endl;
return 0;
}
|
1016 最长上升子序列和
动态规划分析:
- 状态表示: f[i]
- 集合: 所有以 a[i] 结尾的 上升 子序列
- 属性: 和的 max / min / 数量
- 状态计算: 集合划分
- 划分依据: "最后一个不同的点"
- “倒数第二个数”到底是谁 -> 由谁转移而来

代码:
C++ |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 | #include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n;
int w[N];
int f[N];
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i ++ ) scanf("%d", &w[i]);
int res = 0;
for (int i = 0; i < n; i ++ )
{
f[i] = w[i];
for (int j = 0; j < i; j ++ )
if (w[i] > w[j])
f[i] = max(f[i], f[j] + w[i]);
res = max(res, f[i]);
}
printf("%d\n", res);
return 0;
}
|