用有序映射维护有水格子,用最小堆按编号处理爆炸,并在爆炸时更新当前前驱和后继。
OJ: shumeng
题目 ID: CSP202403D
难度:普及+/提高-
标签:模拟有序集合优先队列
日期: 2026-07-31 16:21
形式化题目
一维网格
- 若某格子达到
滴,它立刻清空,并让当前时刻它左右两侧最近的有水格子各加一滴水; - 多个格子同时待爆炸时,按位置从小到大依次爆炸,且新产生的爆炸按相同规则加入待爆序列。
每次操作结束后输出仍有水的格子数。
思路
这题是带连锁反应的模拟,核心要支持两类操作:找出当前格子左右最近的有水格子,以及按位置顺序处理待爆格子。
朴素做法:有序数组暴力模拟
先看一个直觉做法:把所有有水的格子放进排序数组,每次爆炸用二分找位置、删掉它再给相邻元素加水。
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-07-31 16:21
* update_at: 2026-08-17 22:39
*/
// brute.cpp:小数据暴力解,用有序数组保存有水格子,二分查找相邻格子。
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int c, m, operation_count;
cin >> c >> m >> operation_count;
vector<pair<int, int> > water; // water[i] = {位置, 水滴数},按位置升序排列
for (int i = 0; i < m; i++) {
pair<int, int> current;
cin >> current.first >> current.second;
water.push_back(current);
}
sort(water.begin(), water.end());
while (operation_count--) {
int position;
cin >> position;
vector<int> exploding; // 待爆炸的位置列表
// 找到指定位置并加一滴水
for (int i = 0; i < (int)water.size(); i++) {
if (water[i].first == position) {
water[i].second++;
if (water[i].second >= 5) exploding.push_back(position);
break;
}
}
while (!exploding.empty()) {
sort(exploding.begin(), exploding.end()); // 最左的格子先爆
int current = exploding[0];
exploding.erase(exploding.begin());
// 二分找到当前格子在数组中的下标
vector<pair<int, int> >::iterator current_it = lower_bound(
water.begin(), water.end(), make_pair(current, -1));
if (current_it == water.end() || current_it->first != current
|| current_it->second < 5) continue; // 已被之前的爆炸清空
int index = current_it - water.begin();
water.erase(water.begin() + index);
// 左右相邻的有水格子各加一滴水
if (index - 1 >= 0) {
water[index - 1].second++;
if (water[index - 1].second >= 5) exploding.push_back(water[index - 1].first);
}
if (index < (int)water.size()) {
water[index].second++;
if (water[index].second >= 5) exploding.push_back(water[index].first);
}
}
cout << water.size() << '\n';
}
return 0;
}这个做法每步的数组删除是
主解:map 维护相邻 + 小根堆维护爆炸顺序
用 map<位置, 水滴数> 保存所有有水格子,有序键天然给出“左右相邻的有水格子”。一次爆炸时,先记住当前格子的前驱和后继迭代器,再删除当前格子,这样取到的正是爆炸瞬间两侧最近的格子。
待爆炸位置放进小根堆,保证每次处理编号最小的格子。堆里可能出现已被之前爆炸清空的过期位置,弹出时检查它是否仍在 map 中且水滴数
关键性质:一个格子爆开后从有水集合中消失,本轮不会再被重新加入,所以所有爆炸的总次数是
代码
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-07-31 16:21
* update_at: 2026-08-17 22:39
*/
#include <bits/stdc++.h>
using namespace std;
map<int, int> water; // water[pos] 表示位置 pos 当前的水滴数,只保存有水的格子
priority_queue<int, vector<int>, greater<int> > exploding; // 待爆炸位置,小根堆保证最左的先爆
// 给位置 pos 加一滴水,达到 5 滴就加入待爆堆
void add_water(int pos) {
water[pos]++;
if (water[pos] >= 5) exploding.push(pos);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int c, m, operation_count;
cin >> c >> m >> operation_count;
for (int i = 0; i < m; i++) {
int pos, cnt;
cin >> pos >> cnt;
water[pos] = cnt;
}
while (operation_count--) {
int position;
cin >> position;
add_water(position);
// 处理连锁爆炸,直到没有待爆格子
while (!exploding.empty()) {
int current = exploding.top();
exploding.pop();
map<int, int>::iterator it = water.find(current);
if (it == water.end() || it->second < 5) continue; // 已被之前的爆炸清空
// 先记住左右相邻的有水格子,再删除当前格子
map<int, int>::iterator left = it;
bool has_left = (left != water.begin());
if (has_left) --left;
map<int, int>::iterator right = it;
++right;
bool has_right = (right != water.end());
water.erase(it);
if (has_left) add_water(left->first);
if (has_right) add_water(right->first);
}
cout << water.size() << '\n';
}
return 0;
}复杂度
设
- 时间:每个格子至多爆炸一次,每次 map 查找与更新
,总时间复杂度 。 - 空间:map 中最多
个格子,空间复杂度 。
总结
模拟连锁反应时要把握两个关键数据结构:有序集合表达“最近的有水格子”,优先队列表达“多个待爆格子按最左优先”。删除当前节点后再取邻居,能避免把已经清空的格子当成传播目标;堆中残留的过期元素通过合法性检查丢弃即可。
