1 条题解
-
0
题解: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))。可通过“分层固定”策略逐步将元素归位:
- 先固定前 (N-1) 行的列分布(确保每行元素的列号符合目标);
- 再固定列的顺序;
- 最后固定行的顺序。 该策略通过二分图匹配保证每行元素的合法性,移动次数严格控制在 (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执行步骤
- 第一步(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
- 处理第0行
- 第二步(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
- 处理第0列
- 第三步(D操作,(n=2) 次):矩阵已有序,操作后仍保持有序。
- 总移动次数=1+2+2=5 < 3×2=6,获得满分。
- 1
信息
- ID
- 3827
- 时间
- 1000ms
- 内存
- 512MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者