当前位置: 首页 > news >正文

基于贪心最小化包围盒策略的布阵算法

文章目录

  • 问题描述
  • 问题分析
  • 算法实现
    • 代码实现
    • 算法改进

问题描述

在游戏《部落冲突》中,每个玩家都有一些不同占地面积的建筑,如城墙(1 x 1)、地狱之塔(2 x 2)、加农炮(3 x 3)、天鹰火炮(4 x 4)等。而在游戏中,每8个小时会在整个地图的一个随机位置生成一个 2 x 23 x 3 的障碍物,但是如果这个位置上有建筑或者紧邻着一个建筑,则不会生成1。问:如何布置阵型才能使得障碍物成功生成的概率最大?

为了简化问题,我们假设玩家拥有 aaa1 x 1 的建筑、bbb2 x 2 的建筑、ccc3 x 3 的建筑、ddd4 x 4 的建筑,整张地图一共有 n2n^2n2 个格子,其中有 ppp 的概率生成 2 x 2 的障碍物,1−p1-p1p 的概率生成 3 x 3 的障碍物。

注:nnn 是指有效放置建筑的边长,但在这个放置建筑区域外3格也可以生成障碍物,即障碍物可放置的区域面积为 (n+6)2(n+6)^2(n+6)22

问题分析

首先以左上角为坐标原点,建立坐标系:xxx 轴垂直向下为正方向,yyy 轴水平向右为正方向。则障碍物区域 VO={0,1,…,n+5}2V_O=\{0, 1, \dots, n+5\}^2VO={0,1,,n+5}2,建筑区域 VB={3,4,…,n+2}2V_B=\{3, 4, \dots, n+2\}^2VB={3,4,,n+2}2

容易得到,上述问题实际上为2个子问题:

  1. (最大团问题)取 D⊆VBD\sube V_BDVB 使得其面积为 SD=a+4b+9c+16dS_D=a+4b+9c+16dSD=a+4b+9c+16d,且剩余区域 VO∖DV_O\setminus DVOD 的边长为2和边长为3的正方形子集最多。
  2. (正方分割问题)得到 DDD 的一个划分,使得 DDD 分为 aaa 个边长为1的正方形、bbb 个边长为2的正方形、ccc 个边长为3的正方形、ddd 个边长为4的正方形。若不存在这样的划分则回到第1步。

而其中第1步是NP完全的,且在实际游戏中的 n∈(40,50)n\in(40,50)n(40,50),无法在有限时间内求解,因此我们采用贪心算法寻找局部最优解。

首先证明,最优的区域 D⊆VBD\sube V_BDVB 必为一个凸区域且所有的建筑尽可能紧密地布置在一个集中的区域内。

引理:设 D⊆Z2D\sube\Z^2DZ2 为凸集,则 ∣D⊕B∣|D \oplus B|DBP(D)\mathcal{P}(D)P(D) 正相关3,其中 B={0,1,⋯ ,b}2B=\{0,1,\cdots,b\}^2B={0,1,,b}2 方形区域。
:由 Steiner 公式 ∣D⊕B∣=∣D∣+P(D)⋅w(B)+∣B∣|D \oplus B| = |D| + \mathcal{P}(D) \cdot w(B) + |B|DB=D+P(D)w(B)+B,且 BBB 是紧凸集且具有非空内部,故系数 w(B)=1π∫02πhB(θ) dθ>0\displaystyle w(B)=\frac{1}{\pi} \int_{0}^{2\pi} h_B(\theta)\ \mathrm d\theta > 0w(B)=π102πhB(θ) dθ>0,其中 hB(θ)h_B(\theta)hB(θ)BBB 在方向 θ\thetaθ 上的支持函数,故 ∣D⊕B∣|D \oplus B|DBP(D)\mathcal{P}(D)P(D) 正相关。

2 x 2 障碍物可放置的左上角点集合为 A2={0,1,…,n+4}2A_2 = \{0, 1, \dots, n+4\}^2A2={0,1,,n+4}23 x 3 障碍物可放置的左上角点集合为 A3={0,1,…,n+3}2A_3 = \{0, 1, \dots, n+3\}^2A3={0,1,,n+3}2B2,B3B_2,B_3B2,B3 分别表示 2 x 2 结构元素和 3 x 3 结构元素,F2=A2∖(D⊕B2),F3=A3∖(D⊕B3)F_2=A_2 \setminus (D \oplus B_2),F_3=A_3 \setminus (D \oplus B_3)F2=A2(DB2),F3=A3(DB3) 分别表示障碍物可放置的空闲位置集合,则障碍物成功生成的概率为

P(D)=p∣F2∣∣A2∣+(1−p)∣F3∣∣A3∣=p∣F2∣(n+4)2+(1−p)∣F3∣(n+3)2=p∣A2∣−∣D⊕B2∣(n+4)2+(1−p)∣A3∣−∣D⊕B3∣(n+3)2=1−(p(n+4)2∣D⊕B2∣+1−p(n+3)2∣D⊕B3∣)=:1−F(D)(1) \begin{aligned} P(D)&=p\frac{|F_2|}{|A_2|}+(1-p)\frac{|F_3|}{|A_3|}\\ &=p\frac{|F_2|}{(n+4)^2}+(1-p)\frac{|F_3|}{(n+3)^2}\\ &=p \frac{|A_2|-|D \oplus B_2|}{(n+4)^2} + (1-p) \frac{|A_3|-|D \oplus B_3|}{(n+3)^2}\\ &=1-\left(\frac p{(n+4)^2}|D \oplus B_2| + \frac{1-p}{(n+3)^2}|D \oplus B_3|\right)\\ &=:1-F(D) \end{aligned}\tag1 P(D)=pA2F2+(1p)A3F3=p(n+4)2F2+(1p)(n+3)2F3=p(n+4)2A2DB2+(1p)(n+3)2A3DB3=1((n+4)2pDB2+(n+3)21pDB3)=:1F(D)(1)

其中 F(D)F(D)F(D) 为失败的概率。

DDD 不是凸的,则存在两点 p,q∈Dp, q \in Dp,qD 和一点 rrrpppqqq 的线段上,且 r∉Dr \notin Dr/D。考虑将点 ppp 移动到点 rrr 且保持 ∣D∣|D|D 不变。移动后,覆盖正方形的并集变为 ΔF=−∣S(p)∖S(r)∣+∣S(r)∖S(p)∣\Delta F = - |S(p) \setminus S(r)| + |S(r) \setminus S(p)|ΔF=S(p)S(r)+S(r)S(p),其中 S(p)S(p)S(p) 表示覆盖点 ppp 的正方形集合。若 rrr 更靠近 qqq,则 ∣S(r)∪S(q)∣≤∣S(p)∪S(q)∣|S(r) \cup S(q)| \leq |S(p) \cup S(q)|S(r)S(q)S(p)S(q),从而 ∣D⊕B2∣|D \oplus B_2|DB2∣D⊕B3∣|D \oplus B_3|DB3 可能减小。通过反复将点向重心移动,可以减小 F(D)F(D)F(D),最终使 DDD 成为一个连通集。

由于在连续极限下且 VOV_OVO 足够大,损失函数 F(D)F(D)F(D)DDD 的函数且是子模的或具有凸性性质。又 VBV_BVB 是正方形且问题对称,最优 DDD 是一个 centered rectangle,从而是凸的。

故最优建筑布置 DDD 是一个凸区域。

由引理,∣D⊕B∣|D \oplus B|DBP(D)\mathcal{P}(D)P(D) 正相关且凸集具有最小周长,故最优的区域 D⊆VBD\sube V_BDVB 为一个凸区域且具有非空内部,即所有的建筑尽可能紧密地布置在一个集中的区域内。

证毕。

算法实现

由(1)式知,P(D)P(D)P(D) 关于 DDD 具有平移不变性,因此我们可以直接从 VBV_BVB 的左上角开始放置建筑且不影响 P(D)P(D)P(D)

因此,我们可以采取贪心策略:从 VBV_BVB 左上角开始,优先放置大型建筑,避免分散布置,利用紧凑布局将所有建筑集中放置。具体算法如图所示:

边缘对齐优化
紧凑布局核心算法
按尺寸降序
按坐标升序
未放置
找到最优位置
无合法位置
全部放置
计算包围盒
收缩包围盒到左上角
扩展建筑到主区域边界
遍历所有建筑
初始化建筑放置矩阵
计算候选位置
计算禁区覆盖增量
选择最小增量位置
放置建筑
返回失败
更新禁区矩阵
输入参数
初始化建筑列表
建筑排序
4x4建筑优先
同一尺寸按左上角排序
创建禁区覆盖矩阵
输出布局矩阵
计算障碍物生成概率

代码实现

#include <stdint.h>#include <algorithm>
#include <climits>
#include <cmath>
#include <iostream>
#include <limits>  // 用于numeric_limits
#include <vector>using namespace std;// 建筑信息结构体
struct Building {uint8_t size;  // 建筑尺寸(1-4)int count;	   // 建筑数量Building(int s, int c) : size(s), count(c) {}
};// 比较函数:按尺寸降序排列
bool compareSize(const Building &a, const Building &b) {return a.size > b.size;
}// 计算放置建筑后障碍物生成概率的损失
double calcObstacleProbLoss(const vector<vector<bool>> &forbidden, int x, int y, int size, int n, double p, int borderSize = 3) {// forbidden已经包含主区域外borderSize格范围int offset = borderSize;int totalSize = n + (borderSize << 1);// 模拟放置建筑后的临时禁区vector<vector<bool>> tempForbidden = forbidden;// 计算放置建筑后新增的禁区(扩展到周围1格)for (int i = -1; i <= size; ++i) {for (int j = -1; j <= size; ++j) {int nx = x + i + offset;  // 转换到扩展后的坐标系int ny = y + j + offset;if (nx >= 0 && nx < totalSize && ny >= 0 && ny < totalSize) {tempForbidden[nx][ny] = true;}}}int valid2x2Before = 0, valid2x2After = 0;int valid3x3Before = 0, valid3x3After = 0;// 计算放置前2x2障碍物可生成区域for (int gx = 0; gx <= totalSize - 2; ++gx) {for (int gy = 0; gy <= totalSize - 2; ++gy) {bool valid = true;for (int i = 0; i < 2 && valid; ++i) {for (int j = 0; j < 2 && valid; ++j) {int nx = gx + i;int ny = gy + j;if (nx >= 0 && nx < totalSize && ny >= 0 && ny < totalSize && forbidden[nx][ny]) {valid = false;}}}if (valid) valid2x2Before++;}}// 计算放置后2x2障碍物可生成区域for (int gx = 0; gx <= totalSize - 2; ++gx) {for (int gy = 0; gy <= totalSize - 2; ++gy) {bool valid = true;for (int i = 0; i < 2 && valid; ++i) {for (int j = 0; j < 2 && valid; ++j) {int nx = gx + i;int ny = gy + j;if (nx >= 0 && nx < totalSize && ny >= 0 && ny < totalSize && tempForbidden[nx][ny]) {valid = false;}}}if (valid) valid2x2After++;}}// 计算放置前3x3障碍物可生成区域for (int gx = 0; gx <= totalSize - 3; ++gx) {for (int gy = 0; gy <= totalSize - 3; ++gy) {bool valid = true;for (int i = 0; i < 3 && valid; ++i) {for (int j = 0; j < 3 && valid; ++j) {int nx = gx + i;int ny = gy + j;if (nx >= 0 && nx < totalSize && ny >= 0 && ny < totalSize && forbidden[nx][ny]) {valid = false;}}}if (valid) valid3x3Before++;}}// 计算放置后3x3障碍物可生成区域for (int gx = 0; gx <= totalSize - 3; ++gx) {for (int gy = 0; gy <= totalSize - 3; ++gy) {bool valid = true;for (int i = 0; i < 3 && valid; ++i) {for (int j = 0; j < 3 && valid; ++j) {int nx = gx + i;int ny = gy + j;if (nx >= 0 && nx < totalSize && ny >= 0 && ny < totalSize && tempForbidden[nx][ny]) {valid = false;}}}if (valid) valid3x3After++;}}// 计算概率损失(放置前概率 - 放置后概率)double probBefore = p * valid2x2Before + (1 - p) * valid3x3Before;double probAfter = p * valid2x2After + (1 - p) * valid3x3After;return probBefore - probAfter;
}// 检查位置是否有效(能容纳建筑)
bool isValidPosition(const vector<vector<int>> &grid, int x, int y, int size) {if (x + size > grid.size() || y + size > grid[0].size()) return false;// 检查位置是否已被占用for (int i = 0; i < size; ++i)for (int j = 0; j < size; ++j)if (grid[x + i][y + j] != 0) return false;return true;
}// 放置建筑并更新禁区
void placeBuilding(vector<vector<int>> &grid, vector<vector<bool>> &forbidden, int x, int y, int size, int borderSize = 3) {int n = grid.size();int offset = borderSize;int totalSize = n + 2 * borderSize;// 添加边界检查作为额外安全保障if (x + size > n || y + size > n)return;	 // 无效位置,直接返回// 标记建筑位置for (int i = 0; i < size; ++i)for (int j = 0; j < size; ++j)grid[x + i][y + j] = size;// 扩展禁区到周围1格(考虑主区域外borderSize格范围)for (int i = -1; i <= size; ++i) {for (int j = -1; j <= size; ++j) {int nx = x + i + offset;int ny = y + j + offset;if (nx >= 0 && nx < totalSize && ny >= 0 && ny < totalSize) {forbidden[nx][ny] = true;}}}
}// 主布局算法
vector<vector<char>> optimizeLayout(int a, int b, int c, int d, int n, double p = 0.5) {			// 默认p=0.5,可根据需要调整const int borderSize = 3;																		// 主区域外扩展的边界大小(现在是3格)vector<Building> buildings = {Building(4, d), Building(3, c), Building(2, b), Building(1, a)};	// 初始化建筑列表sort(buildings.begin(), buildings.end(), compareSize);											// 按尺寸降序排序vector<vector<int>> grid(n, vector<int>(n, 0));													// 初始化地图// 初始化禁区,包含主区域外borderSize格范围int totalSize = n + 2 * borderSize;vector<vector<bool>> forbidden(totalSize, vector<bool>(totalSize, false));// 标记主区域外的边界部分为非禁区(初始状态)for (auto &b : buildings) {	 // 放置所有建筑for (int i = 0; i < b.count; ++i) {int bestX = -1, bestY = -1;double minLoss = numeric_limits<double>::max();// 遍历所有可能位置for (int x = 0; x < n; ++x) {for (int y = 0; y < n; ++y) {if (isValidPosition(grid, x, y, b.size)) {double loss = calcObstacleProbLoss(forbidden, x, y, b.size, n, p, borderSize);if (loss < minLoss) {  // 选择概率损失最小的位置minLoss = loss;bestX = x;bestY = y;}}}}if (bestX != -1 && bestY != -1) placeBuilding(grid, forbidden, bestX, bestY, b.size, borderSize);  // 放置最佳位置}}// 边缘对齐优化int minX = n, minY = n;for (int x = 0; x < n; ++x)for (int y = 0; y < n; ++y)if (grid[x][y] != 0) {minX = min(minX, x);minY = min(minY, y);}// 创建对齐后的地图vector<vector<char>> res(n, vector<char>(n, '.'));for (int x = 0; x < n; ++x)for (int y = 0; y < n; ++y)if (grid[x][y] != 0) {int newX = x - minX;int newY = y - minY;// 确保新坐标在有效范围内if (newX >= 0 && newX < n && newY >= 0 && newY < n) res[newX][newY] = '0' + grid[x][y];	 // 1-4表示建筑}return res;
}// 测试代码
int main() {int a = 10, b = 5, c = 3, d = 2, n = 10;double p = 0.5;	 // 障碍物生成概率参数:p概率生成2x2障碍物,1-p概率生成3x3障碍物,以 p == 0.5 为例cout << "当前障碍物生成概率:2 x 2 障碍物概率 = " << p << ", 3 x 3 障碍物概率 = " << (1 - p) << endl;auto layout = optimizeLayout(a, b, c, d, n, p);// 输出布局for (const auto &row : layout) {for (char c : row) cout << c << "\t";cout << "\n";}return 0;
}

算法时间复杂度为 O(n2(a+b+c+d))O(n^2(a+b+c+d))O(n2(a+b+c+d))

算法改进

尽管在理论上可以证明最优解是紧密放置的,但这只是一个必要条件,因此上述贪心算法仍可能陷入局部最优,对此可以引入一定随机性,避免陷入局部最优。此外,也可以通过模拟退火或遗传算法等启发式算法以一定概率选择更差解来避免局部最优。


  1. 即生成在离建筑至少1格距离的地方。 ↩︎

  2. 建筑主区域外延伸3格。 ↩︎

  3. ∣D⊕B∣={d+b∣d∈D,b∈B}|D \oplus B|=\{ d + b \mid d \in D, b \in B \}DB={d+bdD,bB} 表示闵可夫斯基和,P(D)\mathcal{P}(D)P(D) 表示 DDD 的周长,即 DDDDcD^cDc 之间的边界边(两个端点分别属于 DDDDcD^cDc)的数量。 ↩︎

http://www.dtcms.com/a/473634.html

相关文章:

  • 《Python 异步数据库访问全景解析:从阻塞陷阱到高性能实践》
  • AI 自己造“乐高积木”:生成式 AI 设计可拼装模块化硬件的实战笔记
  • 10.11笔记
  • 冒泡排序的多种实现方式详解
  • 网页设计平面设计温州网站优化页面
  • 特别分享:聊聊Git
  • M|蝙蝠侠:侠影之谜
  • crawl4ai智能爬虫(一):playwright爬虫框架详解
  • 探究Java、C语言、Python、PHP、C#与C++在多线程编程中的核心差异与应用场景
  • 国外网站模板网站建设ui培训班好
  • 瑞安建设公司网站旅游网站系统的设计与实现
  • MongoDB-基本介绍(一)基本概念、特点、适用场景、技术选型
  • 国产之光金仓数据库,真能平替MongoDB?实测来了!
  • 网站开发需要学什么语言wordpress所有栏目循环输出
  • 低代码革命:拖拽式界面生成器与API网关的深度集成
  • “事件风暴 → 上下文映射 → 模块化”在 ABP vNext 的全链路模板
  • 如何在Linux服务器上部署jenkins?
  • 2.1 阵列信号处理基础
  • Centos7下docker的jenkins下载并配置jdk与maven
  • 网络数据侦探:抓包工具在爬虫开发中的艺术与科学
  • 手搓docker - 实现篇
  • soho做网站谷歌推广网站建设采购项目
  • 深入理解HTTP协议的本质
  • 以太网通信
  • 网站运营推广方式网站建设需要学编程么
  • 开源合规:GPL-3.0项目的专利风险规避
  • Java基于SpringBoot的医院门诊管理系统,附源码+文档说明
  • windows查询与设备通讯的mac地址
  • Tauri Android 开发踩坑实录:从 Gradle 版本冲突到离线构建成功
  • nuxt3中使用defineAsyncComponent懒加载组件,但其中的loadingComponent和errorComponent为什么不生效