1 条题解

  • 0
    @ 2025-11-19 17:56:11

    题解:Square Grid Puzzle(方格矩阵重排问题)

    一、题目核心分析

    1. 问题定义

    给定一个 (N \times N) 的方阵,包含 (0 \sim N^2-1) 的不重复整数。通过两种操作将矩阵转为有序状态(第 (i) 行第 (j) 列元素为 (i \times N + j)):

    • 下移(D操作):移除最上面一行,将一个重新排列的新行添加到矩阵底部;
    • 右移(R操作):移除最左侧一列,将一个重新排列的新列添加到矩阵右侧。 要求移动次数不超过 (3N) 以获得满分。

    2. 核心难点

    • 操作是“全局影响”的:下移/右移会改变整个矩阵的行/列结构,无法局部调整;
    • 有序状态的约束严格:每个元素的最终位置由 (i \times N + j) 唯一确定;
    • 需控制移动次数:超过 (3N) 会扣分,需设计高效的分步策略。

    3. 关键洞察

    每个元素 (x) 的最终位置为 ((x//N, x%N))(行号 = (x//N),列号 = (x%N))。可通过“分层固定”策略逐步将元素归位:

    1. 先固定前 (N-1) 行的列分布(确保每行元素的列号符合目标);
    2. 再固定列的顺序;
    3. 最后固定行的顺序。 该策略通过二分图匹配保证每行元素的合法性,移动次数严格控制在 (3N) 以内。

    二、完整代码

    #include <bits/stdc++.h>
    using namespace std;
    mt19937 rnd((random_device())());
    const int N = 20;
    
    int n;
    int a[N][N];
    vector<int> g[N];
    int match[N];
    bool vis[N];
    
    // 二分图最大匹配DFS:用于判断是否能为第i行元素分配合法列号
    bool dfs(int u) {
        for (int v : g[u]) {
            if (vis[v])
                continue;
            vis[v] = 1;
            if (match[v] == -1 || dfs(match[v])) {
                match[v] = u;
                return 1;
            }
        }
        return 0;
    }
    
    void solve() {
        vector<pair<char, vector<int>>> ans;
    
        // 第一步:固定前n-1行的列分布(通过D操作)
        // 目标:第i行(0<=i<n-1)的元素,其目标列号覆盖0~n-1(无重复)
        for (int i = 0; i + 1 < n; i++) {
            // 构建二分图:左节点=当前第i行元素索引,右节点=目标列号
            for (int j = 0; j < n; j++) {
                g[j].clear();
                int x = a[i][j];
                int target_col = x % n;  // 元素x的目标列号
                // 列号需未被前i行占用(前i行已固定,列号唯一)
                bool flag = true;
                for (int x_prev = 0; x_prev < i; x_prev++) {
                    if (a[x_prev][target_col] % n == target_col) {
                        flag = false;
                        break;
                    }
                }
                if (flag) {
                    g[j].push_back(target_col);
                }
            }
            // 打乱邻接表,增加随机化成功率(避免死循环)
            for (int j = 0; j < n; j++) {
                shuffle(g[j].begin(), g[j].end(), rnd);
            }
            // 初始化匹配数组
            memset(match, -1, sizeof match);
            // 检查是否存在完美匹配(确保每行元素能分配唯一列号)
            bool ok = true;
            for (int x = 0; x < n; x++) {
                memset(vis, 0, sizeof vis);
                if (!dfs(x)) {
                    ok = false;
                    break;
                }
            }
            if (!ok) return;  // 匹配失败,重新尝试(主函数循环调用)
    
            // 生成D操作:按匹配结果重新排列第i行,移到最底部
            vector<int> arr(n);
            for (int x = 0; x < n; x++) {
                arr[match[x]] = a[i][x];
            }
            ans.push_back({'D', arr});
            // 更新矩阵:移除第i行(后续行上移),底部添加新行
            for (int row = i; row < n - 1; row++) {
                memcpy(a[row], a[row + 1], sizeof a[row]);
            }
            memcpy(a[n - 1], arr.data(), sizeof a[n - 1]);
        }
    
        // 第二步:固定列顺序(通过R操作)
        // 目标:第j列元素的目标行号覆盖0~n-1(无重复)
        for (int i = 0; i < n; i++) {
            vector<int> col(n);
            for (int j = 0; j < n; j++) {
                col[j] = a[j][i];
            }
            // 按元素目标行号排序(目标行号=x//n)
            sort(col.begin(), col.end(), [&](int x, int y) {
                return x / n < y / n;
            });
            ans.push_back({'R', col});
            // 更新矩阵:移除第i列(后续列左移),右侧添加新列
            for (int col = i; col < n - 1; col++) {
                for (int row = 0; row < n; row++) {
                    a[row][col] = a[row][col + 1];
                }
            }
            for (int row = 0; row < n; row++) {
                a[row][n - 1] = col[row];
            }
        }
    
        // 第三步:固定行顺序(通过D操作)
        // 目标:每行元素按目标列号排序(最终有序)
        for (int i = 0; i < n; i++) {
            vector<int> row(n);
            for (int j = 0; j < n; j++) {
                row[j] = a[i][j];
            }
            // 按元素目标列号排序(目标列号=x%n)
            sort(row.begin(), row.end(), [&](int x, int y) {
                return x % n < y % n;
            });
            ans.push_back({'D', row});
            // 更新矩阵:移除第i行,底部添加新行
            for (int row = i; row < n - 1; row++) {
                memcpy(a[row], a[row + 1], sizeof a[row]);
            }
            memcpy(a[n - 1], row.data(), sizeof a[n - 1]);
        }
    
        // 输出结果(移动次数<=3n,满足满分要求)
        printf("%d\n", ans.size());
        for (auto &op : ans) {
            printf("%c ", op.first);
            for (int num : op.second) {
                printf("%d ", num);
            }
            printf("\n");
        }
        exit(0);
    }
    
    int main() {
        scanf("%d", &n);
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                scanf("%d", &a[i][j]);
            }
        }
        // 循环调用solve,直到找到合法方案(随机化确保成功率)
        while (true) {
            solve();
        }
        return 0;
    }
    

    三、关键逻辑详解

    1. 二分图匹配的作用(第一步核心)

    (1)二分图构建

    • 左节点:当前处理行(第 (i) 行)的元素索引((0 \sim n-1));
    • 右节点:目标列号((0 \sim n-1),即元素 (x) 的 (x%n));
    • 边的条件:元素 (x) 的目标列号未被前 (i-1) 行占用(确保列号唯一)。

    (2)完美匹配的意义

    若存在完美匹配,说明第 (i) 行的所有元素可分配到唯一的目标列号,且与前 (i-1) 行的列号不冲突。通过D操作将该行移到最底部,相当于“固定”了该行元素的列分布。

    (3)随机化优化

    对邻接表 g[j] 洗牌,避免因固定顺序导致的匹配失败,提高每次 solve 调用的成功率。

    2. 三步核心策略

    (1)第一步:D操作固定前 (n-1) 行的列分布((n-1) 次D操作)

    • 遍历前 (n-1) 行,对每行构建二分图并寻找完美匹配;
    • 按匹配结果重新排列该行,通过D操作移到最底部;
    • 效果:前 (n-1) 行的元素列号覆盖 (0 \sim n-1),且无重复,为后续列排序铺垫。

    (2)第二步:R操作固定列顺序((n) 次R操作)

    • 遍历每一列,提取该列所有元素;
    • 按元素的目标行号((x//n))排序(目标行号决定元素最终所在行);
    • 通过R操作将该列移到最右侧,相当于“固定”了该列元素的行分布;
    • 效果:每列元素的行号覆盖 (0 \sim n-1),且按目标行号排序。

    (3)第三步:D操作固定行顺序((n) 次D操作)

    • 遍历每一行,提取该行所有元素;
    • 按元素的目标列号((x%n))排序(目标列号决定元素最终所在列);
    • 通过D操作将该行移到最底部,完成最终有序化;
    • 效果:每行元素按目标列号排序,矩阵达到有序状态。

    3. 矩阵更新逻辑

    • D操作后:当前行(第 (i) 行)被移除,后续行上移,新行添加到最底部;
    • R操作后:当前列(第 (i) 列)被移除,后续列左移,新列添加到最右侧;
    • 采用 memcpy 高效复制数组,确保矩阵状态正确更新。

    4. 循环重试机制

    主函数中 while(true) 循环调用 solve,因为二分图匹配可能因随机化顺序失败(概率极低),循环重试可确保最终找到合法方案。

    四、移动次数分析

    • 第一步:(n-1) 次D操作;
    • 第二步:(n) 次R操作;
    • 第三步:(n) 次D操作;
    • 总移动次数:((n-1) + n + n = 3n - 1 < 3n),完全满足满分要求(移动次数 < (3N))。

    五、样例验证(以 (N=2) 为例)

    样例输入2

    2
    2 1
    0 3
    

    有序目标

    0 1
    2 3
    

    执行步骤

    1. 第一步(D操作,(n-1=1) 次)
      • 处理第0行 [2,1]
        • 元素2的目标列号=0(2%2=0),元素1的目标列号=1(1%2=1);
        • 二分图完美匹配:2→0,1→1;
        • D操作:D 2 1,矩阵更新为:
          0 3
          2 1
          
    2. 第二步(R操作,(n=2) 次)
      • 处理第0列 [0,2]:目标行号0→0(0//2=0),2→1(2//2=1),排序后为 [0,2]
      • R操作:R 0 2,矩阵更新为:
        3 0
        1 2
        
      • 处理第0列 [3,1]:目标行号3→1(3//2=1),1→0(1//2=0),排序后为 [1,3]
      • R操作:R 1 3,矩阵更新为:
        0 1
        2 3
        
    3. 第三步(D操作,(n=2) 次):矩阵已有序,操作后仍保持有序。
    • 总移动次数=1+2+2=5 < 3×2=6,获得满分。
    • 1

    信息

    ID
    3827
    时间
    1000ms
    内存
    512MiB
    难度
    10
    标签
    递交数
    2
    已通过
    1
    上传者