#L3116. 「IOI2017」诺鲁孜节
「IOI2017」诺鲁孜节
「IOI2017」诺鲁孜节 - 题解
题目概述
给定一个 的网格图,包含空地(.)和岩石(#)。我们需要将一些空地改为灌木(X),使得:
- 所有剩余的空地形成一个连通的无环图(即树结构)
- 最大化可以躲藏小孩的空地数量(叶子节点)
一个小孩可以躲藏在恰好只有一个邻居是空地的空格中。
问题分析
这是一个在网格图上构造最大叶子生成树的问题。关键在于:
- 最终的空地必须形成一棵树
- 叶子节点(度数为1的节点)数量要尽可能多
- 只能在原有空地上进行修改,不能改变岩石
解决思路
核心观察
- 连通分量选择:只能选择一个连通分量来构建树,因为题目要求所有空格连通
- 叶子节点最大化:在度数限制为4的网格树中,理论上叶子节点数可达总节点数的约2/3
- BFS树优化:通过多次随机BFS生成树,选择叶子节点数最多的方案
算法步骤
- 读入并解析网格:识别所有空地和岩石
- 寻找连通分量:使用BFS/DFS找出所有连通的空地区域
- 选择最大分量:通常最大连通分量能提供最多的叶子节点
- 多轮BFS生成树:随机选择根节点和邻居访问顺序,生成多棵BFS树
- 选择最优树:保留叶子节点数最多的BFS树
- 输出结果:将不在最优树中的空地标记为灌木
关键代码实现
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <algorithm>
#include <random>
#include <chrono>
using namespace std;
const int dx[4] = {0, 1, 0, -1};
const int dy[4] = {1, 0, -1, 0};
struct Point {
int x, y;
Point(int x, int y) : x(x), y(y) {}
};
class MazeSolver {
private:
vector<string> grid;
int m, n;
vector<vector<bool>> is_empty;
vector<vector<bool>> in_tree;
vector<vector<int>> children_count;
public:
MazeSolver(const vector<string>& input_grid) : grid(input_grid) {
m = grid.size();
n = grid[0].size();
is_empty = vector<vector<bool>>(m, vector<bool>(n, false));
in_tree = vector<vector<bool>>(m, vector<bool>(n, false));
children_count = vector<vector<int>>(m, vector<int>(n, 0));
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
is_empty[i][j] = (grid[i][j] == '.');
}
}
}
// BFS找连通分量
vector<vector<Point>> find_components() {
vector<vector<Point>> components;
vector<vector<bool>> visited(m, vector<bool>(n, false));
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (is_empty[i][j] && !visited[i][j]) {
vector<Point> component;
queue<Point> q;
q.push(Point(i, j));
visited[i][j] = true;
while (!q.empty()) {
Point p = q.front(); q.pop();
component.push_back(p);
for (int d = 0; d < 4; d++) {
int nx = p.x + dx[d];
int ny = p.y + dy[d];
if (nx >= 0 && nx < m && ny >= 0 && ny < n &&
is_empty[nx][ny] && !visited[nx][ny]) {
visited[nx][ny] = true;
q.push(Point(nx, ny));
}
}
}
components.push_back(component);
}
}
}
return components;
}
// 随机BFS生成树
int random_bfs_tree(const vector<Point>& component, int root_idx) {
// 重置状态
for (auto& p : component) {
in_tree[p.x][p.y] = false;
children_count[p.x][p.y] = 0;
}
// 随机数生成器
unsigned seed = chrono::system_clock::now().time_since_epoch().count();
mt19937 rng(seed);
queue<Point> q;
Point root = component[root_idx];
in_tree[root.x][root.y] = true;
q.push(root);
while (!q.empty()) {
Point u = q.front(); q.pop();
// 随机打乱邻居顺序
vector<int> dirs = {0, 1, 2, 3};
shuffle(dirs.begin(), dirs.end(), rng);
for (int d : dirs) {
int nx = u.x + dx[d];
int ny = u.y + dy[d];
if (nx >= 0 && nx < m && ny >= 0 && ny < n &&
is_empty[nx][ny] && !in_tree[nx][ny]) {
// 将v加入树中作为u的孩子
in_tree[nx][ny] = true;
children_count[u.x][u.y]++;
q.push(Point(nx, ny));
}
}
}
// 统计叶子节点数(没有孩子的节点)
int leaf_count = 0;
for (auto& p : component) {
if (children_count[p.x][p.y] == 0) {
leaf_count++;
}
}
return leaf_count;
}
// 解决主问题
vector<string> solve() {
vector<string> result = grid;
// 找连通分量
vector<vector<Point>> components = find_components();
if (components.empty()) {
// 没有空地,全部标记为X
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (result[i][j] == '.') {
result[i][j] = 'X';
}
}
}
return result;
}
// 选择最大的连通分量
int max_size = 0;
int best_component = 0;
for (int i = 0; i < components.size(); i++) {
if (components[i].size() > max_size) {
max_size = components[i].size();
best_component = i;
}
}
const vector<Point>& main_component = components[best_component];
// 多轮随机BFS
int best_leaf_count = 0;
vector<vector<bool>> best_tree(m, vector<bool>(n, false));
int num_trials = min(100, (int)main_component.size());
for (int trial = 0; trial < num_trials; trial++) {
int root_idx = trial % main_component.size();
int leaf_count = random_bfs_tree(main_component, root_idx);
if (leaf_count > best_leaf_count) {
best_leaf_count = leaf_count;
best_tree = in_tree;
}
}
// 构建最终结果
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (result[i][j] == '.') {
// 只有在最佳树中的空地才保留
if (best_tree[i][j]) {
result[i][j] = '.';
} else {
result[i][j] = 'X';
}
}
}
}
return result;
}
};
int main() {
int n, m, k;
cin >> n >> m >> k;
vector<string> grid(m);
for (int i = 0; i < m; i++) {
cin >> grid[i];
}
MazeSolver solver(grid);
vector<string> result = solver.solve();
for (const string& row : result) {
cout << row << endl;
}
return 0;
}
算法复杂度
- 时间复杂度:,其中 是BFS尝试次数
- 空间复杂度:,用于存储网格和访问状态
优化策略
- 多轮随机化:通过多次随机BFS增加找到更优解的概率
- 分量选择:优先处理最大连通分量
- 邻居随机化:随机访问邻居节点,生成不同的树结构
预期效果
该方法能够在合理时间内找到质量较好的解,对于规模为 的网格,通过100轮随机BFS通常能找到叶子节点数接近理论最大值的解。