这里是paoxiaomo,一个现役ACMer,之后将会持续更新算法笔记系列以及笔试题题解系列
本文章面向想打ICPC/蓝桥杯/天梯赛等程序设计竞赛,以及各个大厂笔试的选手
感谢大家的订阅➕ 和 喜欢💗
有什么想看的算法专题可以私信博主
(本文题面由清隆学长收集)
01.K小姐的礼物选择
问题描述
K小姐在参加一场礼物挑选游戏。游戏中,共有
n
n
n 个礼物排成一排,每个礼物有两个属性:价值和间隔。第
i
i
i 个礼物的价值为
v
a
l
u
e
i
value_i
valuei,在选取这个礼物后,必须跳过接下来的
s
k
i
p
i
skip_i
skipi 个礼物。K小姐在通过每个礼物时必须决定是否选取,且不能回头选择。K小姐希望你帮助她,计算出她能获得的最大价值总和。
输入格式
第一行包含一个正整数
n
n
n,表示礼物的数量。
接下来的
n
n
n 行,每行包含两个正整数
v
a
l
u
e
i
,
s
k
i
p
i
value_i, skip_i
valuei,skipi,分别表示第
i
i
i 个礼物的价值和需要跳过的礼物数量。
输出格式
输出一个整数,表示 K小姐能获得的最大价值总和。
样例输入
3
2 2
1 1
1 1
样例输出
2
评测数据与规模
对于
100
%
100\%
100% 的评测数据,
1
⩽
n
⩽
100000
1 \leqslant n \leqslant 100000
1⩽n⩽100000,
1
⩽
v
a
l
u
e
i
,
s
k
i
p
i
⩽
100000
1 \leqslant value_i, skip_i \leqslant 100000
1⩽valuei,skipi⩽100000。
【试题解析】
这是一个经典的动态规划问题。我们可以使用动态规划来解决。
首先,我们定义一个数组
d
p
dp
dp,其中
d
p
[
i
]
dp[i]
dp[i] 表示在考虑前
i
i
i 个礼物时所能获得的最大总价值。然后,我们可以按顺序考虑每个礼物,更新
d
p
[
i
]
dp[i]
dp[i] 的值。
假设我们当前考虑第
i
i
i 个礼物,那么我们有两种选择:
- 选择第 i i i 个礼物,那么我们的总价值将增加 v a l u e i value_i valuei,并且我们必须跳过接下来的 s k i p i skip_i skipi 个礼物。因此,我们可以从 d p [ i − s k i p i ] dp[i-skip_i] dp[i−skipi] 中获取前 i − s k i p i i-skip_i i−skipi 个礼物的最大总价值,然后加上 v a l u e i value_i valuei,得到选择第 i i i 个礼物时的总价值。
- 不选择第
i
i
i 个礼物,那么我们的总价值将保持不变,继续考虑下一个礼物。
综合这两种情况,我们可以得到状态转移方程:
d p [ i ] = max ( d p [ i − 1 ] , d p [ i − s k i p i ] + v a l u e i ) dp[i] = \max(dp[i-1], dp[i-skip_i] + value_i) dp[i]=max(dp[i−1],dp[i−skipi]+valuei)
最终, d p [ n ] dp[n] dp[n] 即为所求的结果,表示在考虑前 n n n 个礼物时所能获得的最大总价值。
#include <bits/stdc++.h>
using namespace std;
const int maxN = 100010;
using ll = long long;
int n;
vector<vector<ll>> gifts(maxN, vector<ll>(2));
int main() {
cin >> n;
for (int i = 0; i < n; i++) {
int v, s;
cin >> v >> s;
gifts[i][0] = v, gifts[i][1] = s;
}
vector<ll> dp(n, 0);
dp[n - 1] = gifts[n - 1][0];
for (int i = n - 2; i >= 0; i--) {
dp[i] = dp[i + 1];
if (i + gifts[i][1] + 1 >= n) {
dp[i] = max(dp[i], gifts[i][0]);
} else {
dp[i] = max(dp[i], gifts[i][0] + dp[i + gifts[i][1] + 1]);
}
}
cout << dp[0] << endl;
return 0;
}
02.K小姐与A先生的骰子对决
问题描述
K小姐和A先生决定通过掷骰子的方式来一决胜负。他们各自拥有一组特制的骰子,每个骰子的面数不同,分别为
2
2
2 到
8
8
8 面。掷出每一面的概率都是等可能的。他们将同时掷出所有骰子,并将每个骰子朝上的点数相加。点数总和更高的一方获胜,如果点数相同,则判定为平手。K小姐想知道她获胜的概率是多少,请你帮助她计算。
输入格式
第一行包含两个正整数
n
n
n 和
m
m
m,分别代表 K小姐和 A先生各自骰子的数量。
第二行包含
n
n
n 个正整数
a
1
,
a
2
,
…
,
a
n
a_1, a_2, \ldots, a_n
a1,a2,…,an,表示 K小姐每个骰子的面数。
第三行包含
m
m
m 个正整数
b
1
,
b
2
,
…
,
b
m
b_1, b_2, \ldots, b_m
b1,b2,…,bm,表示 A先生每个骰子的面数。
输出格式
输出一行,包含一个保留三位小数的浮点数,表示 K小姐获胜的概率。
样例输入
1 3
8
2 3 4
样例输出
0.255
评测数据与规模
对于全部的测试数据,满足
1
⩽
n
,
m
⩽
20
1 \leqslant n,m \leqslant 20
1⩽n,m⩽20 和
2
⩽
a
i
,
b
i
⩽
8
2 \leqslant a_i,b_i \leqslant 8
2⩽ai,bi⩽8。
【试题解析】
这个问题可以通过暴力搜索或动态规划来解决,但是由于骰子数量可能较大,指数级别复杂度的暴力搜索不太可行,因此我们选择使用动态规划。
首先,我们定义一个数组
d
p
K
dp_K
dpK,其中
d
p
K
[
i
]
dp_K[i]
dpK[i] 表示 K 小姐掷出的点数总和为
i
i
i 的概率。同样地,定义一个数组
d
p
A
dp_A
dpA 表示 A 先生的点数总和为
j
j
j 的概率。
然后,我们按顺序考虑每个骰子的每个面的点数,更新
d
p
K
dp_K
dpK 和
d
p
A
dp_A
dpA 的值。具体地,我们可以这样更新:
- 对于 K 小姐的每个骰子的每个面的点数,更新 d p K dp_K dpK,计算出掷出点数总和为 i i i 的概率。
- 对于 A 先生的每个骰子的每个面的点数,更新
d
p
A
dp_A
dpA,计算出掷出点数总和为
j
j
j 的概率。
最终,我们可以通过 d p K dp_K dpK 和 d p A dp_A dpA 来计算 K 小姐获胜的概率。具体来说,我们可以遍历所有可能的点数总和 i i i 和 j j j,并统计满足 i > j i > j i>j 的情况出现的概率之和。
需要注意的是,初始时, d p K [ 0 ] dp_K[0] dpK[0] 和 d p A [ 0 ] dp_A[0] dpA[0] 的值均为 1 1 1,表示掷出点数总和为 0 0 0 的概率为 1 1 1。然后,按照上述方法逐步更新数组 d p K dp_K dpK 和 d p A dp_A dpA 的值。
最后,将满足条件 i > j i > j i>j 的概率之和作为 K 小姐获胜的概率输出即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 30, M = 200;
int n, m;
int a[N], b[N];
double f[N][M], g[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];
f[0][0] = g[0][0] = 1.0;
for (int i = 1; i <= n; i++) {
for (int j = i; j <= 160; j++) {
for (int k = j - 1; k >= j - a[i] && k >= 0; k--) {
f[i][j] += f[i - 1][k] / a[i];
}
}
}
for (int i = 1; i <= m; i++) {
for (int j = i; j <= 160; j++) {
for (int k = j - 1; k >= j - b[i] && k >= 0; k--) {
g[i][j] += g[i - 1][k] / b[i];
}
}
}
double res = 0, pre = 0;
for (int i = 2; i <= 160; i++) {
pre += g[m][i - 1];
res += f[n][i] * pre;
}
printf("%.3f", res);
return 0;
}
03.羊群重整计划
问题描述
K 小姐拥有若干羊圈,编号从
−
2
×
1
0
9
-2 \times 10^9
−2×109 到
2
×
1
0
9
2 \times 10^9
2×109。部分羊圈中有羊,而另一些则为空。为了便于管理,K 小姐决定将羊群重新安置,使所有的羊最终都圈在连续编号的
n
n
n 个羊圈中。每次操作,她可以将一个羊圈中的所有羊转移到任何一个空的羊圈。请问最少需要多少次操作,才能达成目标?
输入格式
第一行包含一个整数
N
N
N,代表有羊的羊圈数量。
接下来
N
N
N 行,每行一个整数
a
i
a_i
ai,表示有羊的羊圈编号。
输出格式
输出一个整数,表示最少的转移次数。
样例输入
5
2 0 -1 3 6
样例输出
1
评测数据与规模
对于所有评测数据,有
1
≤
N
≤
1
0
5
1 \leq N \leq 10^5
1≤N≤105 和
−
2
×
1
0
9
≤
a
i
≤
2
×
1
0
9
-2 \times 10^9 \leq a_i \leq 2 \times 10^9
−2×109≤ai≤2×109。保证所有的
a
i
a_i
ai 均不相同。
【试题解析】
这是一个关于最少操作次数的问题,可以通过贪心算法解决。
首先,我们将有羊的羊圈按编号排序,然后计算相邻羊圈之间的间隔。
接着,我们找到最大的相邻间隔,这个间隔的长度减去1就是最少需要的操作次数。
例如,假设最大的相邻间隔长度为
k
k
k,那么我们只需将一次羊转移到任何一个空的羊圈,就可以使得这
k
k
k 个羊圈连续。因此,最少需要的操作次数为
k
−
1
k - 1
k−1。
#include <bits/stdc++.h>
using namespace std;
int main() {
int N;
cin >> N;
vector<int> sheep(N);
for (int i = 0; i < N; ++i) {
cin >> sheep[i];
}
sort(sheep.begin(), sheep.end());
int minMoves = 0;
for (int i = 1; i < N; ++i) {
minMoves = max(minMoves, sheep[i] - sheep[i - 1] - 1);
}
cout << minMoves << endl;
return 0;
}
整理试题不易,你的关注是我更新的最大动力!关注博主 带你看更多面试及竞赛试题和实用算法。