官网:https://pintia.cn/problem-sets/994805046380707840/exam/problems/type/7
文章目录
-
- [一、L2-048 寻宝图](#一、L2-048 寻宝图)
- 二、L2-047锦标赛能力值还原
- [三、L2-046 天梯赛的赛场安排](#三、L2-046 天梯赛的赛场安排)
- [四、L2-045 堆宝塔](#四、L2-045 堆宝塔)
一、L2-048 寻宝图
题目大意
给定一张 N × M N \times M N×M 的网格地图:
0代表水域,1代表普通陆地,2~9代表埋藏宝藏的陆地;- 四连通(上下左右相邻)的陆地构成一座岛屿;
- 要求输出两个整数:岛屿总数量、包含至少一个宝藏的岛屿数量。
数据范围: N × M ≤ 10 5 N \times M \le 10^5 N×M≤105
核心思路
这是一道经典的洪水填充 (Flood Fill)问题,用 BFS 或 DFS 均可解决:
- 遍历网格中每一个格子,遇到未访问过的非水域格子,就启动一次连通块遍历,岛屿总数 +1;
- 遍历连通块的过程中,只要发现任意一个格子数值 >
'1'(即有宝藏),就标记该岛屿为"有宝藏"; - 单次连通块遍历结束后,若标记为有宝藏,则宝藏岛屿数 +1。
算法步骤
- 读入地图,用二维数组存储(注意输入是字符型,直接存字符即可);
- 建立访问标记,避免重复统计同一个岛屿;
- 双重循环遍历每个格子:
- 若当前格子不是水域且未访问,启动 BFS;
- BFS 中向四个方向扩展,标记已访问,同时检查是否存在宝藏;
- 遍历结束后输出总岛屿数与宝藏岛屿数。
正解代码
cpp
#include<bits/stdc++.h>
using namespace std;
const int dx[] = {-1, 1, 0, 0};
const int dy[] = {0, 0, -1, 1};
int n, m, cnt = 0, ans = 0;
vector<vector<char>> g;
map<pair<int,int>, bool> mp; // 访问标记,也可以用二维数组优化
void bfs(int sx, int sy) {
bool hasTreasure = false;
queue<pair<int,int>> q;
q.push({sx, sy});
mp[{sx, sy}] = true;
if(g[sx][sy] != '1') hasTreasure = true; // 起点就是宝藏
while(!q.empty()) {
auto [x, y] = q.front();
q.pop();
// 枚举四个方向
for(int i = 0; i < 4; i++) {
int nx = x + dx[i], ny = y + dy[i];
// 越界判断
if(nx < 1 || ny < 1 || nx > n || ny > m) continue;
// 是水域或已访问,跳过
if(g[nx][ny] == '0' || mp[{nx, ny}]) continue;
mp[{nx, ny}] = true;
if(g[nx][ny] != '1') hasTreasure = true;
q.push({nx, ny});
}
}
if(hasTreasure) ans++; // 该岛屿有宝藏
}
int main() {
cin >> n >> m;
g.resize(n + 1, vector<char>(m + 1));
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
cin >> g[i][j];
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
if(g[i][j] != '0' && !mp[{i, j}]) {
bfs(i, j);
cnt++; // 每启动一次BFS,对应一个新岛屿
}
cout << cnt << ' ' << ans;
return 0;
}
二、L2-047锦标赛能力值还原
题目大意
有 2 k 2^k 2k 名选手参加单败淘汰赛,赛制为满二叉树结构:
- 第 i i i 轮共有 2 k − i 2^{k-i} 2k−i 场比赛,每场比赛的两名选手分别来自上一轮两场比赛的胜者;
- 能力值高的选手一定获胜,能力值相同时任意一方获胜;
- 已知每场比赛败者的能力值,以及最终冠军的能力值 w w w;
- 要求还原初始所有选手的能力值,无解则输出
No Solution。
数据范围: 1 ≤ k ≤ 18 1 \le k \le 18 1≤k≤18
核心思路
递归 + 满二叉树构造,从根节点(决赛)向下逐层推导:
- 每场比赛对应满二叉树的一个节点,节点存储本场比赛的「胜者能力值」和「败者能力值」;
- 根节点(决赛)的胜者是最终冠军 w w w,败者是输入给出的决赛败者值;
- 核心性质:每场比赛的胜者能力值 ≥ 败者能力值;
- 当前节点的胜者和败者,恰好是它两个子节点的胜者(子节点的胜者打比赛,赢的成为当前节点胜者,输的成为当前节点败者)。因此有两种分配方式:
- 左子节点胜者 = 当前胜者,右子节点胜者 = 当前败者;
- 左子节点胜者 = 当前败者,右子节点胜者 = 当前胜者。
- 递归尝试两种分配方式,只要有一种能让所有叶子节点都满足胜者≥败者,就为合法解。
算法步骤
- 用数组模拟满二叉树,每个节点存胜者
win和败者lose; - 按轮次输入所有比赛的败者值,填入对应节点;
- 根节点的胜者赋值为 w w w,开始 DFS 递归:
- 若当前节点胜者 < 败者,直接返回不合法;
- 若已经递归到选手层(超出比赛节点范围),返回合法;
- 尝试两种分配方式,递归左右子节点,任意一种成功则返回合法;
- 递归成功则输出叶子比赛层的所有胜者、败者(即初始选手顺序),失败则输出
No Solution。
正解代码
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 1 << 19; // k最大18,节点数2^18-1,开足够空间
struct Node {
int win, lose;
} tre[N];
int k;
bool dfs(int now) {
// 递归边界:超出比赛节点范围,到达选手层,合法
if(now >= (1 << k)) return true;
// 胜者必须 >= 败者,否则不合法
if(tre[now].win < tre[now].lose) return false;
// 尝试第一种分配:左子胜者=当前胜者,右子胜者=当前败者
tre[now*2].win = tre[now].win;
tre[now*2+1].win = tre[now].lose;
if(dfs(now*2) && dfs(now*2+1)) return true;
// 尝试第二种分配:左右交换
tre[now*2].win = tre[now].lose;
tre[now*2+1].win = tre[now].win;
if(dfs(now*2) && dfs(now*2+1)) return true;
return false;
}
int main() {
cin >> k;
// 第i轮对应节点区间 [2^{k-i}, 2^{k-i+1})
for(int i = 1; i <= k; i++) {
for(int j = 1 << (k-i); j < 1 << (k-i+1); j++) {
cin >> tre[j].lose;
}
}
cin >> tre[1].win; // 根节点胜者是最终冠军
if(dfs(1)) {
// 叶子比赛层(第1轮)的win+lose就是初始选手顺序
for(int j = 1 << (k-1); j < (1 << k); j++) {
cout << tre[j].win << ' ' << tre[j].lose;
if(j != (1 << k) - 1) cout << ' ';
}
} else {
cout << "No Solution";
}
return 0;
}
三、L2-046 天梯赛的赛场安排
题目大意
有 N N N 所学校参赛,每个赛场容量为 C C C,安排规则如下:
- 按未安排人数从大到小的顺序处理学校;
- 若当前学校剩余人数 ≥ C \ge C ≥C:新开一个赛场,放入 C C C 人,剩余人数继续排队;
- 若剩余人数 < C < C <C:找编号最小 的、剩余空位 ≥ 该人数的非空赛场,安排进去;找不到则新开一个赛场。
要求输出每所学校需要联系的监考数量(即该校分布在多少个赛场),以及总赛场数。
数据范围: 0 < N ≤ 5000 0 < N \le 5000 0<N≤5000, 10 ≤ C ≤ 50 10 \le C \le 50 10≤C≤50,每校人数 ≤ 500
核心思路
贪心模拟 + 优先队列,分两部分处理:
- 整赛场部分 :每所学校的人数除以 C C C 得到的整数部分,必然是独立的满赛场,直接计入该校监考数和总赛场数;余数部分存入大顶堆,等待后续合并安排。
- 余数部分:用大顶堆维护所有学校的剩余人数,每次取出人数最多的余数,按规则遍历现有非满赛场,找到第一个能放下的就安排;找不到就新开赛场。
算法步骤
- 输入所有学校信息,保存名称与编号;
- 对每所学校计算整赛场数
num / C,计入该校监考数与总赛场数;余数 >0 则加入大顶堆(按人数降序); - 用
vector维护所有非满赛场的已用人数(按开赛场顺序排列,保证编号从小到大); - 循环处理优先队列:
- 取出人数最多的余数,遍历非满赛场列表,找到第一个剩余空位足够的赛场;
- 找到则安排进去,该校监考数 +1,更新赛场已用人数;
- 没找到则新开赛场,总赛场数 +1,该校监考数 +1,加入非满赛场列表。
- 按输入顺序输出每校名称与监考数,最后输出总赛场数。
正解代码
cpp
#include<bits/stdc++.h>
#define pii pair<int,int>
using namespace std;
const int N = 5009;
struct School {
string name;
int id;
} s[N];
int cnt[N]; // 每所学校的监考数
vector<int> rooms; // 非满赛场的已用人数,下标对应赛场编号顺序
int main() {
int n, c;
cin >> n >> c;
int total = 0; // 总赛场数
priority_queue<pii> q; // 大顶堆:(剩余人数, 学校id)
for(int i = 0; i < n; i++) {
int num;
cin >> s[i].name >> num;
s[i].id = i;
int full = num / c;
cnt[i] += full;
total += full;
int rem = num % c;
if(rem > 0) q.push({rem, i});
}
while(!q.empty()) {
auto [x, id] = q.top();
q.pop();
bool found = false;
// 按编号从小到大找第一个能放下的赛场
for(int i = 0; i < rooms.size(); i++) {
if(c - rooms[i] >= x) {
rooms[i] += x;
cnt[id]++;
found = true;
break;
}
}
// 找不到就新开赛场
if(!found) {
total++;
cnt[id]++;
rooms.push_back(x);
}
}
// 按输入顺序输出
for(int i = 0; i < n; i++) {
cout << s[i].name << ' ' << cnt[i] << '\n';
}
cout << total;
return 0;
}
四、L2-045 堆宝塔
题目大意
有 A、B 两根柱子,按顺序处理直径不同的彩虹圈,规则如下:
- 第一个彩虹圈直接放在 A 柱,作为第一座宝塔的基座;
- 取下一个彩虹圈 C:
- 若 C 小于 A 柱顶部圈,直接放到 A 柱上;
- 否则比较 C 与 B 柱顶部圈:
- B 柱为空 或 C 大于 B 柱顶部,放到 B 柱上;
- 否则:将 A 柱当前宝塔作为成品入库,清空 A;再把 B 柱上所有比 C 大的圈逐个移到 A 柱;最后把 C 放到 A 柱。
- 所有圈处理完后,A 柱剩余的算一个成品,B 柱剩余的逐个取下堆成另一个成品。
求成品宝塔的总数量,以及最高宝塔的层数。
数据范围: N ≤ 10 3 N \le 10^3 N≤103
核心思路
双栈模拟 ,用两个 vector 模拟 A、B 两根柱子(back() 对应栈顶),严格按照题目描述的规则逐步骤模拟即可。
算法步骤
- 读入第一个彩虹圈,放入 A 柱;
- 遍历剩余每个彩虹圈 C:
- 若 C < A 栈顶 → 入 A 栈;
- 否则若 B 空 或 C > B 栈顶 → 入 B 栈;
- 否则:
- A 栈成品数 +1,更新最大高度,清空 A 栈;
- 循环将 B 栈中所有比 C 大的元素弹出,压入 A 栈;
- 将 C 压入 A 栈。
- 处理剩余柱子:
- A 非空则成品数 +1,更新最大高度;
- B 非空则成品数 +1,更新最大高度。
- 输出总成品数与最大高度。
正解代码
cpp
#include<bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> A, B;
int ans = 0, mx = 0, x;
cin >> x;
A.push_back(x); // 第一个圈直接放A
for(int i = 1; i < n; i++) {
cin >> x;
if(x < A.back()) {
A.push_back(x);
} else if(B.empty() || B.back() < x) {
B.push_back(x);
} else {
// A柱成品入库
ans++;
mx = max(mx, (int)A.size());
A.clear();
// B中比x大的移到A
while(!B.empty() && B.back() > x) {
A.push_back(B.back());
B.pop_back();
}
A.push_back(x);
}
}
// 处理剩余的A和B
if(!A.empty()) {
ans++;
mx = max(mx, (int)A.size());
}
if(!B.empty()) {
ans++;
mx = max(mx, (int)B.size());
}
cout << ans << " " << mx;
return 0;
}