1 条题解

  • 0
    @ 2025-5-3 17:56:03

    题目分析

    问题描述

    我们有一个由火柴棒组成的n×nn \times n网格,其中:

    • 完整网格包含2n(n+1)2n(n+1)根火柴棒
    • 每个正方形由4条边的火柴棒组成
    • 需要找到最少移除的火柴棒数量,使得网格中不再存在任何完整的正方形

    输入输出

    • 输入:网格大小nn和已移除的火柴棒编号
    • 输出:需要额外移除的最少火柴棒数

    解题思路

    1. 问题建模

    将问题转化为集合覆盖问题

    • 设所有火柴棒集合为EE
    • 每个正方形sis_i对应一个火柴棒子集
    • 目标:选择最小子集EEE' \subseteq E,使得:si,siE\forall s_i, s_i \cap E' \neq \emptyset

    2. 算法选择

    采用IDA*算法(迭代加深搜索+启发式评估):

    • 状态表示:当前已移除的火柴棒集合
    • 启发函数h()h():估计剩余需要移除的火柴棒数h=未被破坏的正方形数h = \text{未被破坏的正方形数}
    • 搜索策略
      while not solution_found:
          max_depth += 1
          if dfs(0, max_depth):
              return max_depth
      

    3. 关键步骤

    1. 预处理所有正方形

      • 对于每个可能的正方形,记录其四条边的火柴棒编号
    2. 可行性检查

      $$\text{check}(s) = \bigwedge_{e \in s} \neg \text{used}[e] $$
    3. IDA*核心

      • 使用深度优先搜索尝试移除火柴棒
      • 通过启发函数剪枝优化搜索

    数学表达

    1. 目标函数

      $$\min |E'| \quad \text{s.t.} \quad \bigwedge_{s \in S} (s \cap E' \neq \emptyset) $$
    2. 启发函数

      $$h(state) = \left| \{ s \in S \mid \text{check}(s) = \text{true} \} \right| $$

    复杂度分析

    • 时间复杂度O(bd)O(b^d),其中bb为分支因子,dd为解深度
    • 空间复杂度O(n3)O(n^3)(存储所有正方形信息)

    代码实现

    #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
    上传者