算法提高课笔记。
目录
- 问题的开始:最长上升子序列
- 思路
- 代码
- 怪盗基德的滑翔翼
- 题意
- 思路
- 代码
- 登山
- 题意
- 思路
- 代码
- 友好城市
- 题意
- 思路
- 代码
- 拦截导弹
- 题意
- 思路
- 代码
- 导弹防御系统
- 题意
- 思路
- 代码
- 最长公共上升子序列
- 题意
- 思路
- 代码
问题的开始:最长上升子序列
原题链接
给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。
输入格式
第一行包含整数 N。
第二行包含 N 个整数,表示完整序列。
输出格式
输出一个整数,表示最大长度。
数据范围
1
≤
N
≤
1000
,
1≤N≤1000,
1≤N≤1000,
−
109
≤
数列中的数
≤
109
−109≤数列中的数≤109
−109≤数列中的数≤109
输入样例
7
3 1 2 1 8 5 6
输出样例
4
思路
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n;
int a[N], f[N];
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ ) cin >> a[i];
for (int i = 1; i <= n; i ++ )
{
f[i] = 1;
for (int j = 1; j < i; j ++ )
if (a[j] < a[i]) f[i] = f[j] + 1;
}
int res = 0;
for (int i = 1; i <= n; i ++ ) res = max(res, f[i]);
cout << res;
}
怪盗基德的滑翔翼
原题链接
怪盗基德是一个充满传奇色彩的怪盗,专门以珠宝为目标的超级盗窃犯。
而他最为突出的地方,就是他每次都能逃脱中村警部的重重围堵,而这也很大程度上是多亏了他随身携带的便于操作的滑翔翼。
有一天,怪盗基德像往常一样偷走了一颗珍贵的钻石,不料却被柯南小朋友识破了伪装,而他的滑翔翼的动力装置也被柯南踢出的足球破坏了。
不得已,怪盗基德只能操作受损的滑翔翼逃脱。
假设城市中一共有N幢建筑排成一条线,每幢建筑的高度各不相同。
初始时,怪盗基德可以在任何一幢建筑的顶端。
他可以选择一个方向逃跑,但是不能中途改变方向(因为中森警部会在后面追击)。
因为滑翔翼动力装置受损,他只能往下滑行(即:只能从较高的建筑滑翔到较低的建筑)。
他希望尽可能多地经过不同建筑的顶部,这样可以减缓下降时的冲击力,减少受伤的可能性。
请问,他最多可以经过多少幢不同建筑的顶部(包含初始时的建筑)?
输入格式
输入数据第一行是一个整数K,代表有K组测试数据。
每组测试数据包含两行:第一行是一个整数N,代表有N幢建筑。第二行包含N个不同的整数,每一个对应一幢建筑的高度h,按照建筑的排列顺序给出。
输出格式
对于每一组测试数据,输出一行,包含一个整数,代表怪盗基德最多可以经过的建筑数量。
数据范围
1
≤
K
≤
100
,
1≤K≤100,
1≤K≤100,
1
≤
N
≤
100
,
1≤N≤100,
1≤N≤100,
0
<
h
<
10000
0<h<10000
0<h<10000
输入样例
3
8
300 207 155 299 298 170 158 65
8
65 158 170 298 299 155 207 300
10
2 1 3 4 5 6 7 8 9 10
输出样例
6
6
9
题意
有一排楼房,怪盗基德可以选择任意一个开始,只能往一个方向跳到比当前所在楼房低的楼房,问他最多能跳多少楼房
思路
因为只能往一个方向跳,做两个方向的最长上升子序列即可
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n;
int a[N], f[N];
int main()
{
int t;
cin >> t;
while (t -- )
{
cin >> n;
for (int i = 1; i <= n; i ++ ) cin >> a[i];
int res = 0;
// 正向
for (int i = 1; i <= n; i ++ )
{
f[i] = 1;
for (int j = 1; j < i; j ++ )
if (a[i] > a[j]) f[i] = max(f[i], f[j] + 1);
res = max(res, f[i]);
}
// 反向
for (int i = n; i; i -- )
{
f[i] = 1;
for (int j = n; j > i; j -- )
if (a[i] > a[j]) f[i] = max(f[i], f[j] + 1);
res = max(res, f[i]);
}
cout << res << '\n';
}
}
登山
原题链接
五一到了,ACM队组织大家去登山观光,队员们发现山上一共有N个景点,并且决定按照顺序来浏览这些景点,即每次所浏览景点的编号都要大于前一个浏览景点的编号。
同时队员们还有另一个登山习惯,就是不连续浏览海拔相同的两个景点,并且一旦开始下山,就不再向上走了。
队员们希望在满足上面条件的同时,尽可能多的浏览景点,你能帮他们找出最多可能浏览的景点数么?
输入格式
第一行包含整数N,表示景点数量。
第二行包含N个整数,表示每个景点的海拔。
输出格式
输出一个整数,表示最多能浏览的景点数。
数据范围
2 ≤ N ≤ 1000 2≤N≤1000 2≤N≤1000
输入样例
8
186 186 150 200 160 130 197 220
输出样例
4
题意
一群人浏览几个景点,满足以下规则:
- 按照编号递增顺序浏览
- 相邻两景点高度不能相同
- 一旦开始下降就不能上升了
目标:最多浏览多少景点
思路
条件1:必须是子序列
条件2、3:路径形状先上升再下降
目标:求所有形状满足要求的子序列长度最大值
上升选择什么不影响我们下降时选择什么,因此两段完全独立,分别求出从左往右和从右往左的上升子序列最大值,求出f[i] + g[i] - 1
即可
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n;
int a[N], g[N], f[N];
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ ) cin >> a[i];
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; i -- )
{
g[i] = 1;
for (int j = n; j > i; j -- )
if (a[j] < a[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);
cout << res;
}
友好城市
原题链接
Palmia国有一条横贯东西的大河,河有笔直的南北两岸,岸上各有位置各不相同的N个城市。
北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同。
每对友好城市都向政府申请在河上开辟一条直线航道连接两个城市,但是由于河上雾太大,政府决定避免任意两条航道交叉,以避免事故。
编程帮助政府做出一些批准和拒绝申请的决定,使得在保证任意两条航线不相交的情况下,被批准的申请尽量多。
输入格式
第1行,一个整数N,表示城市数。
第2行到第n+1行,每行两个整数,中间用1个空格隔开,分别表示南岸和北岸的一对友好城市的坐标。
输出格式
仅一行,输出一个整数,表示政府所能批准的最多申请数。
数据范围
1
≤
N
≤
5000
,
1≤N≤5000,
1≤N≤5000,
0
≤
x
i
≤
10000
0≤x_i≤10000
0≤xi≤10000
输入样例
7
22 4
2 6
10 3
15 12
9 8
17 17
4 2
输出样例
4
题意
河的南北两岸有很多城市,友好城市之间可以建桥,满足以下原则:
- 条件1:每个城市只能建一座桥
- 条件2:桥与桥间不能相交
- 条件3:只能在友好城市间建桥
目标:最多建多少桥
思路
题目直接转化成给出多条边,选择最多的边数两边不相交
按下面自变量大小排序,只要上面的自变量是上升子序列,就一定满足情况
代码
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 5010;
int n;
PII q[N];
int f[N];
int main()
{
cin >> n;
for (int i = 0; i < n; i ++ ) cin >> q[i].first >> q[i].second;
sort(q, q + n);
int res = 0;
for (int i = 0; i < n; i ++ )
{
f[i] = 1;
for (int j = 0; j < i; j ++ )
if (q[i].second > q[j].second) f[i] = max(f[i], f[j] + 1);
res = max(res, f[i]);
}
cout << res;
}
拦截导弹
原题链接
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。
但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。
某天,雷达捕捉到敌国的导弹来袭。
由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数,导弹数不超过1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入格式
共一行,输入导弹依次飞来的高度。
输出格式
第一行包含一个整数,表示最多能拦截的导弹数。
第二行包含一个整数,表示要拦截所有导弹最少要配备的系统数。
数据范围
雷达给出的高度数据是不大于 30000 的正整数,导弹数不超过 1000。
输入样例
389 207 155 300 299 170 158 65
输出样例
6
2
题意
给出一个序列代表不同导弹高度,导弹拦截系统每次只能拦截比上一次拦截的导弹低的
第一问:一次最多拦截多少个导弹
第二问:需要多少拦截系统才能把所有导弹拦截
思路
第一问:就是求最长下降子序列
第二问:贪心,用样例说明
389 207 155 300 299 170 158 65
首先389一定要新开一个系统
然后207有两个选择:
(1)接在现有的某个系统序列之后
(2)创建一个新系统
直觉思路:
要使得每个子序列的末尾最大(这样之后就能接上更大的数)
流程:
从前往后扫描每个数,对于每个数:
- 情况1: 如果现有的子序列的结尾都小于当前数,则创建新子序列
- 情况2: 将当前数放到结尾大于等于它的最小的子序列后面
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n;
int q[N];
int f[N], g[N];
int main()
{
while (cin >> q[n]) n ++ ;
int res = 0;
for (int i = 0; i < n; i ++ )
{
f[i] = 1;
for (int j = 0; j < i; j ++ )
if (q[j] >= q[i]) f[i] = max(f[i], f[j] + 1);
res = max(res, f[i]);
}
cout << res << '\n';
int cnt = 0;
for (int i = 0; i < n; i ++ )
{
int k = 0;
while (k < cnt && g[k] < q[i]) k ++; // g[i]存储第i个子序列末尾的数
g[k] = q[i];
if (k >= cnt) cnt ++ ;
}
cout << cnt << '\n';
}
导弹防御系统
原题链接
为了对抗附近恶意国家的威胁,R 国更新了他们的导弹防御系统。
一套防御系统的导弹拦截高度要么一直严格单调上升要么一直严格单调下降。
例如,一套系统先后拦截了高度为 3 和高度为 4 的两发导弹,那么接下来该系统就只能拦截高度大于 4 的导弹。
给定即将袭来的一系列导弹的高度,请你求出至少需要多少套防御系统,就可以将它们全部击落。
输入格式
输入包含多组测试用例。
对于每个测试用例,第一行包含整数 n,表示来袭导弹数量。
第二行包含 n 个不同的整数,表示每个导弹的高度。
当输入测试用例 n=0 时,表示输入终止,且该用例无需处理。
输出格式
对于每个测试用例,输出一个占据一行的整数,表示所需的防御系统数量。
数据范围
1 ≤ n ≤ 50 1≤n≤50 1≤n≤50
输入样例
5
3 5 2 4 1
0
输出样例
2
样例解释
对于给出样例,最少需要两套防御系统。
一套击落高度为 3,4 的导弹,另一套击落高度为 5,2,1 的导弹。
题意
和拦截导弹的区别就是,有两种拦截系统,一种只能越拦越高,一种只能越拦越低
思路
贪心+dfs爆搜
每次需要判断放到第一类系统还是第二类系统,所以只能用爆搜做
(定义全局变量实时更新or迭代加深都可以做
详见下方代码
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 55;
int n;
int q[N], h[N];
int up[N], down[N];
int ans;
// todo 下方做法为更新全局最小值
void dfs(int u, int su, int sd)
{
if (su + sd >= ans) return;
if (u == n)
{
ans = su + sd;
return;
}
// 上升
int k = 0;
while (k < su && up[k] >= q[u]) k ++ ;
int temp = up[k];
up[k] = q[u];
if (k < su) dfs(u + 1, su, sd);
else dfs(u + 1, su + 1, sd);
up[k] = temp;
// 下降
k = 0;
while (k < sd && down[k] <= q[u]) k ++ ;
temp = down[k];
down[k] = q[u];
if (k < sd) dfs(u + 1, su, sd);
else dfs(u + 1, su, sd + 1);
down[k] = temp;
}
int main()
{
while (cin >> n, n)
{
for (int i = 0; i < n; i ++ ) cin >> q[i];
ans = n;
dfs(0, 0, 0);
cout << ans << '\n';
}
}
// todo 下方做法为迭代加深
bool dfs(int depth, int u, int su, int sd)
{
if (su + sd > depth) return false;
if (u == n) return true;
for (int i = 1; i <= su; i ++ )
if (up[i] < h[u])
{
int t = up[i];
up[i] = h[u];
if (dfs(depth, u + 1, su, sd)) return true;
up[i] = t;
break;
}
up[su + 1] = h[u];
if (dfs(depth, u + 1, su + 1, sd)) return true;
for (int i = 1; i <= sd; i ++ )
if (down[i] > h[u])
{
int t = down[i];
down[i] = h[u];
if (dfs(depth, u + 1, su, sd)) return true;
down[i] = t;
break;
}
down[sd + 1] = h[u];
if (dfs(depth, u + 1, su, sd + 1)) return true;
return false;
}
int main()
{
while (cin >> n, n)
{
for (int i = 0; i < n; i ++ ) cin >> h[i];
int depth = 0;
while (!dfs(depth, 0, 0, 0)) depth ++ ;
cout << depth << '\n';
}
}
最长公共上升子序列
原题链接
熊大妈的奶牛在小沐沐的熏陶下开始研究信息题目。
小沐沐先让奶牛研究了最长上升子序列,再让他们研究了最长公共子序列,现在又让他们研究最长公共上升子序列了。
小沐沐说,对于两个数列 A 和 B,如果它们都包含一段位置不一定连续的数,且数值是严格递增的,那么称这一段数是两个数列的公共上升子序列,而所有的公共上升子序列中最长的就是最长公共上升子序列了。
奶牛半懂不懂,小沐沐要你来告诉奶牛什么是最长公共上升子序列。
不过,只要告诉奶牛它的长度就可以了。
数列 A 和 B 的长度均不超过 3000。
输入格式
第一行包含一个整数 N,表示数列 A,B 的长度。
第二行包含 N 个整数,表示数列 A。
第三行包含 N 个整数,表示数列 B。
输出格式
输出一个整数,表示最长公共上升子序列的长度。
数据范围
1 ≤ N ≤ 3000 1≤N≤3000 1≤N≤3000,序列中的数字均不超过 2 31 − 1 2^{31}−1 231−1。
输入样例
4
2 2 1 3
2 1 2 3
输出样例
2
题意
找两个序列的最长公共上升子序列
思路
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 3010;
int n;
int a[N], b[N];
int f[N][N];
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ ) cin >> a[i];
for (int i = 1; i <= n; i ++ ) cin >> b[i];
for (int i = 1; i <= n; i ++ )
{
int maxx = 1;
for (int j = 1; j <= n; j ++ )
{
f[i][j] = f[i - 1][j];
if (a[i] == b[j]) f[i][j] = max(f[i][j], maxx);
if (a[i] > b[j]) maxx = max(maxx, f[i - 1][j] + 1);
}
}
int res = 0;
for (int i = 1; i <= n; i ++ ) res = max(res, f[n][i]);
cout << res;
}