1 条题解
-
0
题目分析
问题描述
我们有一个由火柴棒组成的网格,其中:
- 完整网格包含根火柴棒
- 每个正方形由4条边的火柴棒组成
- 需要找到最少移除的火柴棒数量,使得网格中不再存在任何完整的正方形
输入输出
- 输入:网格大小和已移除的火柴棒编号
- 输出:需要额外移除的最少火柴棒数
解题思路
1. 问题建模
将问题转化为集合覆盖问题:
- 设所有火柴棒集合为
- 每个正方形对应一个火柴棒子集
- 目标:选择最小子集,使得:
2. 算法选择
采用IDA*算法(迭代加深搜索+启发式评估):
- 状态表示:当前已移除的火柴棒集合
- 启发函数:估计剩余需要移除的火柴棒数
- 搜索策略:
while not solution_found: max_depth += 1 if dfs(0, max_depth): return max_depth
3. 关键步骤
-
预处理所有正方形:
- 对于每个可能的正方形,记录其四条边的火柴棒编号
-
可行性检查:
$$\text{check}(s) = \bigwedge_{e \in s} \neg \text{used}[e] $$ -
IDA*核心:
- 使用深度优先搜索尝试移除火柴棒
- 通过启发函数剪枝优化搜索
数学表达
-
目标函数:
$$\min |E'| \quad \text{s.t.} \quad \bigwedge_{s \in S} (s \cap E' \neq \emptyset) $$ -
启发函数:
$$h(state) = \left| \{ s \in S \mid \text{check}(s) = \text{true} \} \right| $$
复杂度分析
- 时间复杂度:,其中为分支因子,为解深度
- 空间复杂度:(存储所有正方形信息)
代码实现
#include <cstdio> #include <cstring> #include <vector> using namespace std; const int N = 61; // 网格最大是 5 * 5 的,其中最多会有 5 * (5 + 1) * 2 = 60 个正方形,所以要开到 61 int n, idx; // n 为网格规模,idx 为正方形数量 int max_depth; // IDA* 的 max_depth vector<int> square[N]; // 存每个正方形边上的火柴的编号 bool used[N]; // 存每个火柴是否已经被破坏 // 新加一个左上角坐标为 (r, c),边长为 len 的正方形 void add(int r, int c, int len) { int d = n << 1 | 1; // 由于用到的 2n + 1 比较多,这里先用一个变量代替掉 2n + 1 vector<int> &s = square[idx]; s.clear(); // 有多组测试数据,需要上一组数据的内容清空 for (int i = 0; i < len; i ++ ) { s.push_back(1 + r * d + c + i); // 上边第 i 个 s.push_back(1 + (r + len) * d + c + i); // 下边第 i 个 s.push_back(1 + n + r * d + c + i * d); // 左边第 i 个 s.push_back(1 + n + r * d + c + i * d + len); // 右边第 i 个 } idx ++ ; } // 判断正方形 s 是否完整 bool check(vector<int> &s) { for (int i = 0; i < (int)s.size(); i ++ ) if (used[s[i]]) return false; // 如果其中有一条边已经被破坏了,那么说明不完整 return true; // 如果每条边都没被破坏,说明完整 } // 估价函数 int f() { static bool backup[N]; // 由于要改动 used,需要先新建一个备份数组 memcpy(backup, used, sizeof used); // 将 used 复制到备份数组中 int res = 0; for (int i = 0; i < idx; i ++ ) // 枚举所有正方形 if (check(square[i])) // 如果某个正方形是完整的, { res ++ ; // 那么 res ++ ,并将该正方形所有的边都删去 for (int j = 0; j < (int)square[i].size(); j ++ ) used[square[i][j]] = true; } memcpy(used, backup, sizeof used); // 复制回来 return res; } // IDA* bool dfs(int depth) { if (depth + f() > max_depth) return false; for (int i = 0; i < idx; i ++ ) // 枚举所有的正方形 if (check(square[i])) // 如果第 i 个正方形还没被破坏 {// 那么枚举该正方形的所有边编号,去掉该边并继续爆搜 for (int j = 0; j < (int)square[i].size(); j ++ ) { used[square[i][j]] = true; if (dfs(depth + 1)) return true; used[square[i][j]] = false; } return false;// 如果每条边都爆搜不成功,那么说明删掉 max_depth 个火柴无法破坏该正方形 } return true; // 如果所有的正方形都被破坏了,返回 true } int main() { int T;scanf("%d",&T); while (T -- ) { scanf("%d", &n), idx = 0; // 初始化 idx memset(used, false, sizeof used); // 初始化 used for (int len = 1; len <= n; len ++ ) // 枚举 len, r, c,预处理每个正方形 for (int r = 0; r + len <= n; r ++ ) for (int c = 0; c + len <= n; c ++ ) add(r, c, len); int k;scanf("%d", &k); while (k -- ) // 读入所有已经被破坏的边 { int x;scanf("%d", &x); used[x] = true; } max_depth = 0; // IDA* while (!dfs(0)) max_depth ++ ; printf("%d\n", max_depth); } return 0; }
- 1
信息
- ID
- 85
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者