这里是paoxiaomo,一个现役ACMer,之后将会持续更新算法笔记系列以及笔试题题解系列
本文章面向想打ICPC/蓝桥杯/天梯赛等程序设计竞赛,以及各个大厂笔试的选手
感谢大家的订阅➕ 和 喜欢💗
有什么想看的算法专题可以私信博主
(本文题面由清隆学长收集)
题目描述
k小姐很喜欢这 M 和 T 这两个字母。现在k小姐拿到了一个仅由大写字母组成字符串,她可以最多操作 k k k 次,每次可以修改任意一个字符。k小姐想知道,操作结束后最多共有多少个’M’和’T’字符?
输入描述
第一行输入两个正整数 n n n 和 k k k,代表字符串长度和操作次数。
第二行输入一个长度为 n n n 的、仅由大写字母组成的字符串。
输出描述
输出操作结束后最多共有多少个 M 和 T 字符。
示例
输入
5 2
MTUAN
输出
4
说明
修改第三个和第五个字符,形成的为MTTAM,这样共有 4 个 M 和 T。
【题目解析】
这个问题实际上是要求在给定字符串中,通过最多进行 k k k次操作,使得字符串中最多包含多少个’M’和’T’字符。其中,每次操作可以修改字符串中的任意一个字符。
首先,我们需要统计字符串中’M’和’T’字符的数量,分别记为count_M
和count_T
。然后,我们计算在不进行操作的情况下,最多可以有多少个’M’和’T’字符,即二者中较小的那个数,记为max_MT
。
接下来,我们分两种情况讨论:
-
如果 k k k大于或等于’M’和’T’字符数量的差值的绝对值,即 ∣ c o u n t M − c o u n t T ∣ |count_M - count_T| ∣countM−countT∣,那么无论如何操作,我们都无法使得’M’和’T’字符的数量相等,此时最多的’M’和’T’字符数量就是字符串中’M’和’T’字符数量的总和,即
count_M + count_T
。 -
如果 k k k小于’M’和’T’字符数量的差值的绝对值,那么我们可以通过操作使得’M’和’T’字符的数量相等。此时,最多的’M’和’T’字符数量就是 2 × ( m a x M T + k ) 2 \times (max_MT + k) 2×(maxMT+k),其中 m a x M T max_MT maxMT是在不进行操作时,最多的’M’和’T’字符的数量。
cpp
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main() {
int n, k;
cin >> n >> k;
string s;
cin >> s;
int count_M = count(s.begin(), s.end(), 'M');
int count_T = count(s.begin(), s.end(), 'T');
int max_MT = min(count_M, count_T);
if (k >= abs(count_M - count_T))
cout << count_M + count_T << endl;
else
cout << 2 * (max_MT + k) << endl;
return 0;
}
02.k小姐的最大最小值
题目描述
k小姐拿到了一个由正整数组成的数组,但其中有一些元素是未知的(用 0 来表示)。现在k小姐想知道,如果那些未知的元素在区间 [ l , r ] [l,r] [l,r] 范围内随机取值的话,数组所有元素之和的最小值和最大值分别是多少?共有 q q q 次询问。
输入描述
第一行输入三个正整数 n n n、 q q q、 k k k,分别代表数组长度、询问次数和数组中可能出现的最大值。
接下来 n n n 行,每行一个整数,表示数组中的元素,其中可能存在未知的元素,用 0 表示。
接下来 q q q 行,每行两个整数 l l l 和 r r r,表示一次查询的区间范围。
输出描述
对于每个询问,输出一行,包含两个整数,表示数组所有元素之和的最小值和最大值,以空格分隔。
示例
输入
5 2 3
2
0
4
0
5
1 3
2 4
输出
6 14
9 17
【题目解析】
这个问题实际上是要求对于给定数组,计算在区间 [ l , r ] [l, r] [l,r] 内随机取值时,数组所有元素之和的最小值和最大值。
首先,我们需要遍历数组,找到所有未知元素(值为 0)的位置,记录它们的下标。然后,对于每次查询,我们需要计算数组在查询区间内所有已知元素的和,以及未知元素的个数。接着,根据未知元素的个数和可能出现的最大值 k k k,可以计算出最小值和最大值。
假设 s s s 表示查询区间内已知元素的和, c c c 表示查询区间内未知元素的个数,则数组所有元素之和的最小值为 s s s,最大值为 s + c × k s + c \times k s+c×k,对每个查询,计算查询区间内已知元素的和以及未知元素的个数,然后根据这些信息计算出最小值和最大值,并输出结果。
cpp
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, q, k;
cin >> n >> q >> k;
vector<int> arr(n);
vector<int> unknownIndices;
// Input the array
for (int i = 0; i < n; ++i) {
cin >> arr[i];
if (arr[i] == 0)
unknownIndices.push_back(i);
}
for (int i = 0; i < q; ++i) {
int l, r;
cin >> l >> r;
int sumKnown = 0;
int countUnknown = 0;
for (int j = l - 1; j <= r - 1; ++j) {
if (arr[j] != 0)
sumKnown += arr[j];
else
countUnknown++;
}
int minSum = sumKnown;
int maxSum = sumKnown + countUnknown * k;
cout << minSum << " " << maxSum << endl;
}
return 0;
}
03.k小姐的完美矩形
题目描述
k小姐拿到了一个 n × m n \times m n×m 的矩阵,其中每个元素是 0 或者 1。
k小姐认为一个矩形区域是完美的,当且仅当该区域内 0 的数量等于 1 的数量。现在,k小姐希望你回答有多少个 i × i i \times i i×i 的完美矩形区域。你需要回答 1 ≤ i ≤ n 1 \leq i \leq n 1≤i≤n 的所有答案。
输入描述
第一行输入一个正整数 n n n,代表矩阵的行数。
接下来的 n n n 行,每行输入一个长度为 m m m 的 01 串,用来表示矩阵。
输出描述
输出 n n n 行,第 i i i 行输出 i × i i \times i i×i 的完美矩形区域的数量。
示例
输入
4
1010
0101
1100
0011
输出
0
7
0
1
【试题解析】
这个问题可以通过动态规划来解决。我们可以定义一个二维数组 dp[i][j]
,其中 dp[i][j]
表示以矩阵中第 i
行、第 j
列元素为右下角的最大完美矩形的边长。然后,我们遍历矩阵中的每个元素,计算以当前元素为右下角的最大完美矩形的边长,最后累加每个边长对应的数量即可。
具体算法如下:
- 初始化二维数组
dp
,并将所有元素初始化为 0。 - 遍历矩阵中的每个元素,如果当前元素为 0,则更新
dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1
。 - 统计以每个边长为 i i i 的完美矩形的数量,并输出结果。
cpp
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n;
cin >> n;
vector<vector<int>> matrix(n, vector<int>(n));
vector<vector<int>> dp(n, vector<int>(n, 0));
for (int i = 0; i < n; ++i) {
string row;
cin >> row;
for (int j = 0; j < n; ++j) {
matrix[i][j] = row[j] - '0';
}
}
for (int i = 0; i < n; ++i) {
dp[i][0] = matrix[i][0];
}
for (int j = 0; j < n; ++j) {
dp[0][j] = matrix[0][j];
}
for (int i = 1; i < n; ++i) {
for (int j = 1; j < n; ++j) {
if (matrix[i][j] == 0) {
dp[i][j] = min({dp[i-1][j], dp[i][j-1], dp[i-1][j-1]}) + 1;
}
}
}
vector<int> perfectRectangles(n + 1, 0);
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
for (int k = 1; k <= dp[i][j]; ++k) {
perfectRectangles[k]++;
}
}
}
for (int i = 1; i <= n; ++i) {
cout << perfectRectangles[i] << endl;
}
return 0;
}
04.k小姐删除区间
题目描述
k小姐拿到了一个大小为 n n n 的数组,她希望删除一个区间后,使得剩余所有元素的乘积尾部至少有 k k k 个 0。k小姐想知道,一共有多少种不同的删除方案?
输入描述
第一行输入两个正整数 n , k n,k n,k,表示数组的长度和尾部 0 的个数要求。
第二行输入 n n n 个正整数 a i a_i ai,代表k小姐拿到的数组。
1 ≤ n , k ≤ 1 0 5 1 \leq n, k \leq 10^5 1≤n,k≤105
1 ≤ a i ≤ 1 0 9 1 \leq a_i \leq 10^9 1≤ai≤109
输出描述
一个整数,代表删除的方案数。
样例
输入
5 2
2 5 3 4 20
输出
4
说明
第一个方案,删除[3]
第二个方案,删除[4]
第三个方案,删除[3,4]
第四个方案,删除[2]
【试题解析】
这个问题可以通过数学推导来解决,具体步骤如下:
- 首先,我们需要计算数组尾部连续的 0 的个数,记为
trailingZeros
。 - 然后,我们从数组的末尾开始向前遍历,找到第一个连续的子数组,使得该子数组的乘积尾部至少有
k
个 0。 - 接着,我们从该子数组的起始位置开始向前遍历,统计满足条件的删除方案数量。对于每个可能的删除位置,可以通过排列组合的方式计算出其对应的方案数。
- 最后,将每个可能的删除位置的方案数累加起来,即为总的删除方案数。
首先输入数组的长度n
和尾部 0 的个数要求k
,然后输入数组的元素。接着,计算数组尾部连续的 0 的个数trailingZeros
。随后,从数组末尾开始向前遍历,找到第一个满足条件的子数组,并计算其删除方案数。最后,将所有删除方案数累加起来。
#include <iostream>
#include <vector>
using namespace std;
// Function to calculate trailing zeros of a number
int countTrailingZeros(int num) {
int count = 0;
while (num % 10 == 0) {
count++;
num /= 10;
}
return count;
}
int main() {
int n, k;
cin >> n >> k;
vector<int> arr(n);
for (int i = 0; i < n; ++i) {
cin >> arr[i];
}
int trailingZeros = 0;
for (int i = n - 1; i >= 0; --i) {
if (arr[i] == 0)
trailingZeros++;
else
break;
}
long long totalWays = 0;
long long currentWays = 1;
int zeroCount = 0;
for (int i = n - trailingZeros - 1; i >= 0; --i) {
if (arr[i] == 0) {
zeroCount++;
} else {
currentWays *= (zeroCount + 1); // Calculate the number of ways for current deletion position
totalWays += currentWays; // Accumulate total ways
zeroCount = 0;
}
}
cout << totalWays << endl;
return 0;
}
05.k小姐的人际关系
题目描述
小美认为,在人际交往中,随着时间的流逝,朋友的关系也会慢慢变淡,最终朋友关系可能会被淡忘。现在初始有一些朋友关系,存在一些事件会导致两个人淡忘了他们的朋友关系。小美想知道某一时刻中,某两人是否可以通过朋友介绍互相认识。
事件共有 2 种:
u v
: 代表编号 u u u 的人和编号 v v v 的人淡忘了他们的朋友关系。u v
: 代表小美查询编号 u u u 的人和编号 v v v 的人是否能通过朋友介绍互相认识。 注:介绍可以有多层,比如 2 2 2号把 1 1 1 号介绍给 3 3 3 号,然后 3 3 3 号再把 1 1 1 号介绍给 4 4 4 号,这样 1 1 1号和 4 4 4 号就认识了。
输入描述
第一行输入三个正整数 n , m , q n, m, q n,m,q,代表总人数,初始的朋友关系数量,发生的事件数量。
接下来的 m m m 行,每行输入两个正整数 u , v u, v u,v,代表初始编号 u u u 的人和编号 v v v 的人是朋友关系。
接下来的 q q q 行,每行输入三个正整数 o p , u , v op, u, v op,u,v,含义如题目描述所述。
1 ≤ n ≤ 1 0 9 1 \leq n \leq 10^9 1≤n≤109
1 ≤ m , q ≤ 1 0 5 1 \leq m, q \leq 10^5 1≤m,q≤105
1 ≤ u , v ≤ n 1 \leq u, v \leq n 1≤u,v≤n
1 ≤ o p ≤ 2 1 \leq op \leq 2 1≤op≤2
输出描述
对于每次第二种操作,输出一行字符表示查询的答案。
如果编号 u u u 的人和编号 v v v 的人能通过朋友介绍互相认识,则输出"Yes"。否则输出"No"。
样例
输入
5 3 5
1 2
2 3
4 5
1 1 5
2 1 3
2 1 4
1 1 2
2 1 3
输出
Yes
No
No
【试题解析】
这个问题可以通过并查集来解决。我们可以使用并查集维护朋友关系,然后根据事件进行操作。
具体算法如下:
- 初始化并查集,将每个人都初始化为一个单独的集合。
- 遍历初始的朋友关系,将每对朋友关系合并到同一个集合中。
- 根据事件类型进行相应的操作:
- 如果是淡忘事件,则将对应的两个人从它们所在的集合中分离。
- 如果是查询事件,则判断两个人是否属于同一个集合,如果是,则它们可以通过朋友介绍互相认识,否则不能。
首先输入总人数n
,初始的朋友关系数量m
,以及发生的事件数量q
。然后,使用并查集初始化初始的朋友关系。接着,根据事件类型进行相应的操作:如果是淡忘事件,则将对应的两个人从它们所在的集合中分离;如果是查询事件,则判断两个人是否属于同一个集合,如果是,则输出"Yes",否则输出"No"。
cpp
#include <iostream>
#include <vector>
using namespace std;
class UnionFind {
private:
vector<int> parent;
vector<int> rank;
public:
UnionFind(int n) {
parent.resize(n + 1);
rank.resize(n + 1, 0);
for (int i = 1; i <= n; ++i) {
parent[i] = i;
}
}
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
void unite(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX == rootY) return;
if (rank[rootX] < rank[rootY]) {
parent[rootX] = rootY;
} else if (rank[rootX] > rank[rootY]) {
parent[rootY] = rootX;
} else {
parent[rootY] = rootX;
rank[rootX]++;
}
}
};
int main() {
int n, m, q;
cin >> n >> m >> q;
UnionFind uf(n);
// Initialize friend relationships
for (int i = 0; i < m; ++i) {
int u, v;
cin >> u >> v;
uf.unite(u, v);
}
// Process events
for (int i = 0; i < q; ++i) {
int op, u, v;
cin >> op >> u >> v;
if (op == 1) {
// Forgetting event
uf.unite(u, v); // Simply unite to "forget" their friendship
} else {
// Query event
if (uf.find(u) == uf.find(v)) {
cout << "Yes" << endl;
} else {
cout << "No" << endl;
}
}
}
return 0;
}
整理试题不易,你的关注是我更新的最大动力!关注博主 带你看更多面试及竞赛试题和实用算法。