#L3116. 「IOI2017」诺鲁孜节

「IOI2017」诺鲁孜节

「IOI2017」诺鲁孜节 - 题解

题目概述

给定一个 m×nm \times n 的网格图,包含空地(.)和岩石(#)。我们需要将一些空地改为灌木(X),使得:

  1. 所有剩余的空地形成一个连通的无环图(即树结构)
  2. 最大化可以躲藏小孩的空地数量(叶子节点)

一个小孩可以躲藏在恰好只有一个邻居是空地的空格中。

问题分析

这是一个在网格图上构造最大叶子生成树的问题。关键在于:

  • 最终的空地必须形成一棵树
  • 叶子节点(度数为1的节点)数量要尽可能多
  • 只能在原有空地上进行修改,不能改变岩石

解决思路

核心观察

  1. 连通分量选择:只能选择一个连通分量来构建树,因为题目要求所有空格连通
  2. 叶子节点最大化:在度数限制为4的网格树中,理论上叶子节点数可达总节点数的约2/3
  3. BFS树优化:通过多次随机BFS生成树,选择叶子节点数最多的方案

算法步骤

  1. 读入并解析网格:识别所有空地和岩石
  2. 寻找连通分量:使用BFS/DFS找出所有连通的空地区域
  3. 选择最大分量:通常最大连通分量能提供最多的叶子节点
  4. 多轮BFS生成树:随机选择根节点和邻居访问顺序,生成多棵BFS树
  5. 选择最优树:保留叶子节点数最多的BFS树
  6. 输出结果:将不在最优树中的空地标记为灌木

关键代码实现

#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;
}

算法复杂度

  • 时间复杂度O(T(mn))O(T \cdot (mn)),其中 TT 是BFS尝试次数
  • 空间复杂度O(mn)O(mn),用于存储网格和访问状态

优化策略

  1. 多轮随机化:通过多次随机BFS增加找到更优解的概率
  2. 分量选择:优先处理最大连通分量
  3. 邻居随机化:随机访问邻居节点,生成不同的树结构

预期效果

该方法能够在合理时间内找到质量较好的解,对于规模为 1024×10241024 \times 1024 的网格,通过100轮随机BFS通常能找到叶子节点数接近理论最大值的解。