refactor(test.cpp): 简化代码并实现蒙特卡洛模拟

移除地图生成相关代码,改为实现木棍折断问题的蒙特卡洛模拟
使用 C++11 随机数生成工具提高随机数质量
添加 IO 加速设置优化性能
This commit is contained in:
Zengtudor 2025-09-08 21:31:51 +08:00
parent b7d76322ab
commit b8c88b06cd

View File

@ -1,247 +1,67 @@
#include <iostream> #include <iostream>
#include <vector> #include <vector>
#include <string>
#include <random> #include <random>
#include <queue>
#include <algorithm> #include <algorithm>
#include <iomanip>
// --- 常量定义 --- // 使用 C++11 的随机数生成工具,比 rand() 质量更高
// 使用 enum class 增强类型安全和代码可读性 std::mt19937 rng(std::chrono::steady_clock::now().time_since_epoch().count());
enum class TileType : char {
WALL = '#',
FLOOR = '.',
HOUSE_SPACE = 'H', // 临时标记,最终会变回 FLOOR
EXTERIOR = ' ' // 临时标记,用于洪水填充
};
// --- 辅助结构体:矩形房间 --- /**
struct Rect { * @brief [min, max]
int x, y, w, h; * @param min
* @param max
* @return
*/
double random_double(double min, double max) {
std::uniform_real_distribution<double> dist(min, max);
return dist(rng);
}
// 检查此矩形是否与另一个矩形相交(包括边缘接触)
bool intersects(const Rect& other) const {
return (x < other.x + other.w && x + w > other.x &&
y < other.y + other.h && y + h > other.y);
}
// 获取中心点坐标
std::pair<int, int> center() const {
return {x + w / 2, y + h / 2};
}
};
// --- 地图生成器类 ---
class MapGenerator {
public:
MapGenerator(int width, int height)
: width_(width), height_(height),
// 使用 std::random_device 获取真随机数种子,初始化梅森旋转算法
rng_(std::random_device{}()) {
if (width_ <= 0 || height_ <= 0) {
throw std::invalid_argument("Map dimensions must be positive.");
}
}
// 主生成函数
void generate(int max_rooms, int min_room_size, int max_room_size) {
// 1. 初始化地图,全部填充为墙壁
initializeMap();
// 2. 随机放置房间
createRooms(max_rooms, min_room_size, max_room_size);
// 3. 按顺序连接所有房间
createCorridors();
// 4. 识别并填充“房屋空间”
identifyAndFillHouseSpaces();
}
// 打印地图到控制台
void printMap() const {
for (int y = 0; y < height_; ++y) {
for (int x = 0; x < width_; ++x) {
std::cout << static_cast<char>(map_[y][x]);
}
std::cout << std::endl;
}
}
private:
int width_, height_;
std::vector<std::vector<TileType>> map_;
std::vector<Rect> rooms_;
std::mt19937 rng_; // 高质量随机数生成器
// 步骤1: 初始化地图,全部设置为墙壁
void initializeMap() {
map_.assign(height_, std::vector<TileType>(width_, TileType::WALL));
}
// 步骤2: 创造不重叠的房间
void createRooms(int max_rooms, int min_room_size, int max_room_size) {
rooms_.clear();
std::uniform_int_distribution<> size_dist(min_room_size, max_room_size);
std::uniform_int_distribution<> x_dist(1, width_ - max_room_size - 1);
std::uniform_int_distribution<> y_dist(1, height_ - max_room_size - 1);
// 尝试放置 max_rooms 个房间
for (int i = 0; i < max_rooms; ++i) {
Rect new_room;
new_room.w = size_dist(rng_);
new_room.h = size_dist(rng_);
new_room.x = x_dist(rng_);
new_room.y = y_dist(rng_);
// 确保房间在地图边界内
if (new_room.x + new_room.w >= width_ - 1 || new_room.y + new_room.h >= height_ - 1) {
continue;
}
// 检查新房间是否与已存在的房间重叠
bool overlaps = false;
for (const auto& room : rooms_) {
if (new_room.intersects(room)) {
overlaps = true;
break;
}
}
if (!overlaps) {
carveRoom(new_room);
rooms_.push_back(new_room);
}
}
}
// 在地图上“挖掘”一个房间
void carveRoom(const Rect& room) {
for (int y = room.y; y < room.y + room.h; ++y) {
for (int x = room.x; x < room.x + room.w; ++x) {
map_[y][x] = TileType::FLOOR;
}
}
}
// 步骤3: 连接房间
void createCorridors() {
if (rooms_.size() < 2) return;
// 按房间位置排序,以连接邻近的房间
std::sort(rooms_.begin(), rooms_.end(), [](const Rect& a, const Rect& b){
return (a.x + a.y) < (b.x + b.y);
});
for (size_t i = 0; i < rooms_.size() - 1; ++i) {
auto [x1, y1] = rooms_[i].center();
auto [x2, y2] = rooms_[i+1].center();
carvePath(x1, y1, x2, y2);
}
}
// 挖掘一条 L 形路径连接两个点
void carvePath(int x1, int y1, int x2, int y2) {
// 随机决定先水平挖还是先垂直挖
if (std::uniform_int_distribution<>(0, 1)(rng_) == 0) {
carveHorizontalTunnel(x1, x2, y1);
carveVerticalTunnel(y1, y2, x2);
} else {
carveVerticalTunnel(y1, y2, x1);
carveHorizontalTunnel(x1, x2, y2);
}
}
void carveHorizontalTunnel(int x1, int x2, int y) {
for (int x = std::min(x1, x2); x <= std::max(x1, x2); ++x) {
map_[y][x] = TileType::FLOOR;
}
}
void carveVerticalTunnel(int y1, int y2, int x) {
for (int y = std::min(y1, y2); y <= std::max(y1, y2); ++y) {
map_[y][x] = TileType::FLOOR;
}
}
// 步骤4: 关键步骤 - 识别并填充房屋空间
void identifyAndFillHouseSpaces() {
// 使用洪水填充算法BFS标记所有外部空间
// 从 (0,0) 开始,所有能到达的墙壁都被认为是外部的一部分
std::queue<std::pair<int, int>> q;
q.push({0, 0});
// 创建一个访问记录数组,防止重复处理
std::vector<std::vector<bool>> visited(height_, std::vector<bool>(width_, false));
visited[0][0] = true;
map_[0][0] = TileType::EXTERIOR; // 标记为外部
// 定义 4 个移动方向
int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};
while (!q.empty()) {
auto [cx, cy] = q.front();
q.pop();
for (int i = 0; i < 4; ++i) {
int nx = cx + dx[i];
int ny = cy + dy[i];
// 检查边界
if (nx < 0 || ny < 0 || nx >= width_ || ny >= height_) continue;
// 如果邻居是墙壁且未被访问,则将其标记为外部并加入队列
if (!visited[ny][nx] && map_[ny][nx] == TileType::WALL) {
visited[ny][nx] = true;
map_[ny][nx] = TileType::EXTERIOR;
q.push({nx, ny});
}
}
}
// 遍历整个地图,将在上一步中未被标记为 EXTERIOR 的所有 WALL 包围的区域,
// 即原始的 FLOOR 区域,识别为 HOUSE_SPACE。
// 但根据您的要求,房屋空间也用空地表示,所以我们只需把 EXTERIOR 变回 WALL
// 把未被触及的 WALL 留作围墙,而所有 FLOOR 区域自然就是我们想要的。
// 这一步之后,我们实际上已经区分了 "内部墙" 和 "外部墙"。
// 最终清理:将临时标记转换回最终的瓦片类型
for (int y = 0; y < height_; ++y) {
for (int x = 0; x < width_; ++x) {
if (map_[y][x] == TileType::EXTERIOR) {
map_[y][x] = TileType::WALL; // 将外部标记恢复为墙
}
// 所有原本是 FLOOR 的地方,现在可以被理解为 房屋空间 或 走廊,
// 并且都用 '.' 表示,完美符合要求。
}
}
}
};
// --- 主函数 ---
int main() { int main() {
// 设置地图参数 // 设置 IO 加速,在 OI/ACM 竞赛中常用
const int MAP_WIDTH = 80; std::ios_base::sync_with_stdio(false);
const int MAP_HEIGHT = 40; std::cin.tie(NULL);
const int MAX_ROOMS = 15;
const int MIN_ROOM_SIZE = 5;
const int MAX_ROOM_SIZE = 10;
try { // --- 问题参数 ---
// 创建生成器实例 const double L = 3.0; // 木棍总长度
MapGenerator generator(MAP_WIDTH, MAP_HEIGHT); const int NUM_TRIALS = 10000000; // 模拟试验次数,次数越多结果越接近理论值
// 生成地图 int success_count = 0; // 能够构成三角形的成功次数
generator.generate(MAX_ROOMS, MIN_ROOM_SIZE, MAX_ROOM_SIZE);
// 打印结果 // --- 蒙特卡洛模拟 ---
std::cout << "--- Generated Map ---" << std::endl; for (int i = 0; i < NUM_TRIALS; ++i) {
std::cout << "#: Wall, .: Floor/House Space" << std::endl; // 1. 在木棍上随机选择两个断点
generator.printMap(); double break1 = random_double(0.0, L);
double break2 = random_double(0.0, L);
} catch (const std::exception& e) { // 2. 确定两个断点的位置,方便计算三段长度
std::cerr << "Error: " << e.what() << std::endl; double p1 = std::min(break1, break2);
return 1; double p2 = std::max(break1, break2);
// 3. 计算三段的长度
double seg1 = p1;
double seg2 = p2 - p1;
double seg3 = L - p2;
// 4. 检查是否能构成三角形
// 充要条件:任意一边的长度都小于总长度的一半
double half_L = L / 2.0;
if (seg1 < half_L && seg2 < half_L && seg3 < half_L) {
success_count++;
} }
}
// --- 输出结果 ---
double probability = static_cast<double>(success_count) / NUM_TRIALS;
std::cout << "--- 蒙特卡洛模拟解决木棍折断问题 ---" << std::endl;
std::cout << "木棍长度 (L): " << L << std::endl;
std::cout << "模拟次数 (N): " << NUM_TRIALS << std::endl;
std::cout << "成功次数: " << success_count << std::endl;
std::cout << std::fixed << std::setprecision(6);
std::cout << "模拟得到的概率 P ≈ " << probability << std::endl;
std::cout << "理论概率 P = 0.25" << std::endl;
return 0; return 0;
} }