这里是paoxiaomo,一个现役ACMer,之后将会持续更新算法笔记系列以及笔试题题解系列
本文章面向想打ICPC/蓝桥杯/天梯赛等程序设计竞赛,以及各个大厂笔试的选手
感谢大家的订阅➕ 和 喜欢💗
有什么想看的算法专题可以私信博主
(本文题面由清隆学长收集)
01.K小姐的华丽灯光秀
问题描述
K小姐是一位著名的灯光设计师。她最近正在筹备一场盛大的灯光秀活动。该活动场地由 n n n 排、每排 m m m 个的座位组成。每个座位上都安装有一盏可控灯,能够根据指令点亮或熄灭。
为了营造华丽的视觉效果,K小姐希望在某些阶段让场地中出现一些特殊的 2 × 2 2\times 2 2×2 矩形区域,使得该区域内点亮和熄灭的灯光数量完全相等。她想知道总共有多少种不同的 2 × 2 2\times 2 2×2 矩形区域满足这个要求。
输入格式
第一行包含两个正整数 n n n 和 m m m,分别表示场地的行数和列数。
接下来
n
n
n 行,每行包含
m
m
m 个字符,每个字符为 0
或 1
,表示该座位上的灯光当前状态(0
表示熄灭,1
表示点亮)。
输出格式
输出一个整数,表示满足要求的 2 × 2 2\times 2 2×2 矩形区域的总数。
样例输入
2 3
110
010
样例输出
1
数据范围
- 2 ≤ n , m ≤ 100 2 \leq n, m \leq 100 2≤n,m≤100
【题目解析】
为了满足题目要求,我们需要遍历所有可能的 2 × 2 2\times 2 2×2 矩形区域,并检查每个区域内点亮和熄灭的灯光数量是否相等。
对于每个 2 × 2 2\times 2 2×2 的矩形区域,我们可以将其左上角的坐标设为 ( i , j ) (i, j) (i,j),其中 0 ≤ i ≤ n − 2 0 \leq i \leq n-2 0≤i≤n−2 且 0 ≤ j ≤ m − 2 0 \leq j \leq m-2 0≤j≤m−2。然后,我们统计该区域内点亮和熄灭的灯光数量,并检查是否相等。
cpp
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<vector<char>> lights(n, vector<char>(m));
// 读取灯光状态
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
cin >> lights[i][j];
}
}
int count = 0;
// 遍历所有可能的2*2矩形区域
for (int i = 0; i < n - 1; ++i) {
for (int j = 0; j < m - 1; ++j) {
// 统计当前矩形区域内点亮和熄灭的灯光数量
int light_count = 0;
int off_count = 0;
for (int x = i; x <= i + 1; ++x) {
for (int y = j; y <= j + 1; ++y) {
if (lights[x][y] == '1')
light_count++;
else
off_count++;
}
}
// 如果点亮和熄灭的灯光数量相等,则符合要求
if (light_count == off_count)
count++;
}
}
cout << count << endl;
return 0;
}
02.K 小姐的字符串修改
问题描述
K 小姐是一家公司的产品经理,她最近在设计一个新的密码生成系统。为了提高系统的安全性,她决定要求用户在注册时输入一个字符串 s s s,并将其中的某些字符删除,使得最终的字符串不包含长度为偶数的回文子串。
K 小姐希望删除的字符数量尽可能少,因此她想请你帮助她计算出最少需要删除多少个字符。
输入格式
第一行包含一个正整数 n n n,表示字符串 s s s 的长度。
第二行包含一个长度为 n n n 的字符串 s s s。
输出格式
输出一个整数,表示最少需要删除的字符数量。
样例输入
5
aaabc
样例输出
2
数据范围
对于所有的测试数据,满足 1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1≤n≤105。
字符串 s s s 仅由小写英文字母组成。
【题目解析】
我们可以使用贪心的思路来解决这个问题。具体做法是:从左到右扫描字符串,当遇到与前一个字符相同的字符时,就将其删除。这样可以保证最终的字符串中不会出现长度为偶数的回文子串。
例如,对于样例输入 aaabc
,我们首先遇到三个连续的 a
,那么我们需要删除其中的两个 a
,使得剩下的字符串为 abc
。这样就不会出现长度为偶数的回文子串了。
时间复杂度为 O ( n ) O(n) O(n),空间复杂度为 O ( 1 ) O(1) O(1),其中 n n n 是字符串 s s s 的长度。
cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
int n;
cin >> n;
string s;
cin >> s;
char prev = ' ';
int ans = 0;
for (char c : s) {
if (c == prev) {
ans++;
}
prev = c;
}
cout << ans << endl;
return 0;
}
03.A 先生的玩具摆放
问题描述
A 先生是一位资深的玩具收藏家,他有一个长度为 n n n 的玩具架,每个位置上都摆放着一个玩具。A 先生的玩具分为红色和蓝色两种,他希望将所有同色玩具摆放在一起,以方便欣赏。
A 先生可以交换任意两个红色玩具的位置,但不能移动蓝色玩具。他希望在尽可能少的交换次数下,使得玩具架上的玩具按颜色排序,即红色玩具在前,蓝色玩具在后。请你帮助 A 先生计算出最少需要交换多少次。
输入格式
第一行包含一个正整数 n n n,表示玩具架的长度。
第二行包含 n n n 个正整数 a 1 , a 2 , … , a n a_1, a_2, \ldots, a_n a1,a2,…,an,表示每个位置上玩具的编号。
第三行包含一个长度为
n
n
n 的字符串,每个字符表示对应位置上玩具的颜色,用 R
表示红色,用 B
表示蓝色。
输出格式
输出一个整数,表示最少需要交换的次数。如果无法完成排序,输出 -1
。
样例输入
4
1 3 2 4
BRRB
样例输出
1
数据范围
对于所有的测试数据,满足 1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1≤n≤105。
玩具编号均为正整数,且互不相同。
【题目解析】
我们可以使用贪心的思路来解决这个问题。具体做法是:从左到右扫描玩具架,对于每个红色玩具,如果它的位置不正确,那么我们就尝试将它与正确位置上的红色玩具交换。如果正确位置上是蓝色玩具,那么就无法完成排序,输出 -1
。
例如,对于样例输入 1 3 2 4, BRRB
,我们首先扫描到第一个红色玩具 3
,它应该放在第二个位置,所以我们将它与第二个位置上的玩具 2
交换一次。此时玩具架变为 1 2 3 4, BRBR
,满足要求。
时间复杂度为 O ( n ) O(n) O(n),空间复杂度为 O ( n ) O(n) O(n),其中 n n n 是玩具架的长度。
cpp
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> nums(n);
for (int i = 0; i < n; i++) {
cin >> nums[i];
}
string colors;
cin >> colors;
int redIdx = 0;
int ans = 0;
for (int i = 0; i < n; i++) {
if (colors[i] == 'R') {
while (redIdx < n && colors[redIdx] != 'R') {
redIdx++;
}
if (redIdx == n) {
cout << -1 << endl;
return 0;
}
if (nums[i] != nums[redIdx]) {
swap(nums[i], nums[redIdx]);
ans++;
}
redIdx++;
}
}
cout << ans << endl;
return 0;
}
04.K小姐的密码设计
LYA 小姐的花园整理
问题描述
LYA 小姐是一位资深的园艺爱好者,她拥有一个巨大的花园。花园中种植了许多不同种类的花卉,分布在不同的区域。为了便于管理和赏花,LYA 小姐希望将花园分割成若干个连续的区域,使得每个区域内花卉的丰富度不小于一个给定的阈值 k k k。
我们定义一个区域内花卉的丰富度为:该区域内不同花卉种类的数量乘以该区域的面积。例如,一个区域面积为 6 6 6 平方米,种植了矢车菊、玫瑰和百合三种花卉,那么该区域的丰富度就是 3 × 6 = 18 3 \times 6 = 18 3×6=18。
请你帮助 LYA 小姐计算出,最多能将花园分割成多少个满足要求的区域。
输入格式
第一行包含两个正整数 n n n 和 k k k,分别表示花园的总面积(单位:平方米)和最小丰富度要求。
第二行是一个字符串,按顺序给出了花园中每一段区域的面积和种植的花卉种类,其中:
- 小写字母表示该区域种植的花卉种类
- 括号内的数字表示该种花卉所占的区域面积
例如,a(2)b(3)c(1)
表示该区域总面积为
6
6
6 平方米,前
2
2
2 平方米种植了 a
种花卉,接着
3
3
3 平方米种植了 b
种花卉,最后
1
1
1 平方米种植了 c
种花卉。
输出格式
如果无法将花园分割成任何一个满足要求的区域,请输出 -1
。否则,输出最多能分割出的区域数量。
样例输入 1
7 6
a(2)b(2)a(3)
样例输出 1
2
样例输入 2
1000000000 5
x(1000000000)
样例输出 2
200000000
数据范围
- 对于所有数据,满足 1 ≤ k , n ≤ 1 0 18 1 \leq k, n \leq 10^{18} 1≤k,n≤1018
- 输入字符串长度不超过 1 0 6 10^6 106,只包含小写字母、数字和括号
【题目解析】
我们可以使用贪心和二分查找的思路来解决这个问题。具体做法如下:
-
首先遍历输入字符串,将连续的相同花卉种类合并,得到一个列表
a
。例如,对于输入a(2)b(3)a(1)
,我们将得到a = [3, 3]
,表示前 3 3 3 平方米种植了第一种花卉,后 3 3 3 平方米种植了第二种花卉。 -
定义三个变量
res
,cnt
,lens
分别表示区域数量、当前区域内不同花卉种类数和当前区域面积。初始值分别为 0 , 0 , 0 0,0,0 0,0,0。 -
遍历列表
a
,对于每个元素v
:- 如果将它加入当前区域后,区域丰富度仍然小于
k
k
k,那么就将它加入当前区域,更新
cnt
和lens
。 - 否则,我们需要从当前区域切出一个新区域。使用二分查找,找出最大的
x
x
x,使得
(cnt + 1) * (lens + x) < k
。- 将
res
加 1 1 1,表示新增一个区域。 - 将
cnt
和lens
更新为 1 1 1 和v - x$,表示新区域只有一种花卉,面积为
v - x`。 - 如果
lens >= k
,那么可以从新区域中再切出lens // k
个满足要求的区域,将res
加上lens // k
,并将lens
更新为lens % k
。 - 如果
lens
为 0 0 0,就将cnt
置为 0 0 0,否则置为 1 1 1。
- 将
- 如果将它加入当前区域后,区域丰富度仍然小于
k
k
k,那么就将它加入当前区域,更新
-
如果
res
为 0 0 0,说明无法分割出任何满足要求的区域,输出-1
。否则输出res
。
cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
long long n, k;
cin >> n >> k;
string s;
cin >> s;
// 合并连续相同花卉种类
vector<long long> a;
char last = 0;
long long cnt = 0;
for (char c : s) {
if (isalpha(c)) {
if (c != last) {
a.push_back(cnt);
cnt = 0;
last = c;
}
cnt++;
} else {
cnt *= stoll(string(1, c));
}
}
a.push_back(cnt);
// 贪心+二分查找
long long res = 0, curCnt = 0, curLen = 0;
for (long long length : a) {
if ((curCnt + 1) * (curLen + length) < k) {
curCnt += 1;
curLen += length;
} else {
long long l = 1, r = length;
while (l < r) {
long long mid = (l + r) / 2;
if ((curCnt + 1) * (curLen + mid) < k) {
l = mid + 1;
} else {
r = mid;
}
}
res += 1;
curCnt = 1;
curLen = length - l + 1;
if (curLen >= k) {
res += curLen / k;
curLen %= k;
}
curCnt = curLen == 0 ? 0 : 1;
}
}
cout << (res == 0 ? -1 : res) << endl;
return 0;
}
05.K小姐的树林漫步
问题描述
K小姐在一座充满魔法的树林中漫步,每次她都会选择一条新的路径,以探索这片未知的森林。树林中的每个节点都有其独特的风景,现在K小姐想要从节点 s s s 前往节点 t t t,欣赏不同的景致。由于路径选择的多样性,她希望知道,能够成功抵达目的地的概率是多少。
由于树林的神秘性,她可能会进行多次询问。每次询问将会给出她的起始点和目的地,请计算并输出她成功到达目的地的概率。概率结果需要取模 1 0 9 + 7 10^9+7 109+7 后返回。假设答案为分数 a b \frac{a}{b} ba( a , b a, b a,b 互质),您需要找出一个整数 x x x 使得 b ⋅ x ≡ a ( m o d 1 0 9 + 7 ) b \cdot x \equiv a \pmod{10^9+7} b⋅x≡a(mod109+7),且 0 ≤ x < 1 0 9 + 7 0 \le x < 10^9+7 0≤x<109+7。
输入格式
第一行包含一个正整数 n n n,表示树林中的节点个数。
接下来的 n − 1 n-1 n−1 行,每行包含两个整数 u , v u, v u,v,代表树林中的一条路径。
之后一行包含一个正整数 q q q,代表询问的次数。
接下来的 q q q 行,每行包含两个整数 s , t s, t s,t,表示每次询问的起始节点和目的地。
输出格式
输出包含 q q q 行,每行包含一个整数,代表对应询问的概率取模 1 0 9 + 7 10^9+7 109+7 的结果。
样例输入
3
1 2
1 3
2
2 3
1 3
样例输出
1
500000004
数据范围
- 对于 100 % 100\% 100% 的数据,保证 1 ≤ n , q ≤ 2 × 1 0 5 1 \le n, q \le 2 \times 10^5 1≤n,q≤2×105。
- 节点编号从 1 1 1 到 n n n。
【题目解析】
这个问题可以看作是在一棵树上进行路径计数的问题。给定一棵树和两个节点,需要计算从起始节点到目标节点的路径数目。
考虑到树的特性,我们可以使用动态规划来解决这个问题。我们可以从树的根节点开始,自底向上计算每个节点的子树中到达目标节点的路径数目,然后将这些信息传递到父节点,最终得到整棵树的路径数目。
- 使用邻接表构建树的数据结构,记录每个节点的相邻节点。
- 使用动态规划,自底向上计算每个节点的子树中到达目标节点的路径数目。
- 对于每个询问,根据预先计算好的路径数目,计算起始节点到目标节点的路径数目,并取模 1 0 9 + 7 10^9+7 109+7 后输出。
cpp
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
const int MOD = 1000000007;
void dfs(int node, int parent, const vector<vector<int>>& tree, vector<int>& sub_paths, vector<int>& total_paths) {
sub_paths[node] = 1;
for (int neighbor : tree[node]) {
if (neighbor != parent) {
dfs(neighbor, node, tree, sub_paths, total_paths);
sub_paths[node] = (sub_paths[node] + sub_paths[neighbor]) % MOD;
total_paths[node] = (total_paths[node] + total_paths[neighbor]) % MOD;
}
}
total_paths[node] = (total_paths[node] + sub_paths[node]) % MOD;
}
int main() {
int n;
cin >> n;
vector<vector<int>> tree(n + 1);
for (int i = 0; i < n - 1; ++i) {
int u, v;
cin >> u >> v;
tree[u].push_back(v);
tree[v].push_back(u);
}
vector<int> sub_paths(n + 1, 0); // 子树中到达目标节点的路径数目
vector<int> total_paths(n + 1, 0); // 整棵树中到达目标节点的路径数目
dfs(1, 0, tree, sub_paths, total_paths);
int q;
cin >> q;
while (q--) {
int s, t;
cin >> s >> t;
// 对于每个询问,计算起始节点到目标节点的路径数目,并取模后输出
int paths = (total_paths[s] - sub_paths[t] + MOD) % MOD;
cout << paths << endl;
}
return 0;
}
整理试题不易,你的关注是我更新的最大动力!关注博主 带你看更多面试及竞赛试题和实用算法。