1 条题解
-
0
题目分析
题意简述
我们有一个 3 行 n 列的网格,里面有一条蛇形路径,从 1 到 3n 依次填满了所有格子。现在部分格子的数字被擦除了,已知部分格子的数字,要求还原出完整的蛇形路径。
核心观察
- 蛇形结构:蛇在 3×n 的网格中蜿蜒前进,相邻段在网格中也是相邻的(共享边)
- 已知约束:部分格子的数字已知,还原时必须匹配这些已知数字
- 构造性质:在 3×n 的网格中,蛇的路径有特定的模式,通常是"之字形"或"S形"前进
解题思路
这道题的核心是分治+动态规划+回溯构造:
-
状态定义:使用
line[op][x][num][y][add]记录从位置 (x,y) 开始,数字为 num,方向为 add 时能连续匹配的长度 -
方向处理:蛇可以向左或向右移动,用 add=1 表示递增方向,add=0 表示递减方向
-
多种路径模式:
- 之字形:在左右两列之间来回穿梭
- 端线模式:在两端之间形成特定模式
- 混合模式:结合前两种模式
-
双向搜索:同时从网格的左右两端进行搜索,寻找匹配的路径
-
回溯构造:找到可行解后,通过保存的选择信息回溯构造完整路径
代码实现
#include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <cmath> using namespace std; inline int read() { int ans = 0, a = getchar(); while ('0' > a || a > '9') a = getchar(); while ('0' <= a && a <= '9') ans = ans * 10 + a - '0', a = getchar(); return ans; } // 状态数组:line[op][x][num][y][add] 记录连续匹配长度 short line[2][1005][3005][3][3]; short mp[2][1005][3]; // 存储网格数据,op=0是正序,op=1是逆序 int n; bool flg = 0; // 标记是否进入构造阶段 // 检查并赋值函数 bool check(short &x, short y) { if (flg) { if (!x) x = y; // 构造阶段:填充未知格子 } return (!x || !y || (x == y)); // 检查阶段:验证是否匹配 } // 方向函数 inline int g(int x) { return (x == 1 ? 1 : -1); } // 计算从(x,y)开始能连续匹配的长度 short count(int op, int x, int y, int num, int add) { if (x >= n || num <= 0 || num > 3 * n) return 0; short &nw = line[op][x][num][y][add]; if (nw != -1) return nw; if (!check(mp[op][x][y], num)) return nw = 0; return nw = 1 + count(op, x + 1, y, num + g(add), add); } // 判断从x到xx的区间是否能匹配 bool judge(int op, int x, int y, int num, int xx, int add) { if (flg) { // 构造阶段:直接填充格子 for (int i = x; i <= xx; i++) { check(mp[op][i][y], num); num += g(add); } return 1; } // 检查阶段:验证区间匹配 return count(op, x, y, num, add) >= (xx - x + 1); } // 之字形路径检查 bool zigzag(int op, int x, int y, int xx, int add) { int yy = 2 - y; // 另一列 if (add) { // 递增方向 int num = 3 * x + 1; if (!judge(op, x, y, num, xx, add)) return 0; num += (xx - x) * 2 + 1; if (!judge(op, x, 1, num, xx, 1 - add)) return 0; num++; if (!judge(op, x, yy, num, xx, add)) return 0; } else { // 递减方向 int num = 3 * (n - x); if (!judge(op, x, y, num, xx, add)) return 0; num -= (xx - x) * 2 + 1; if (!judge(op, x, 1, num, xx, 1 - add)) return 0; num--; if (!judge(op, x, yy, num, xx, add)) return 0; } return 1; } // 端线模式检查 char endlin[2][1005][3005][3][3]; bool endline(int op, int x, int y1, int y2, int num) { if (x == n) return abs(y2 - y1) == 1; // 终点检查 char &w = endlin[op][x][num][y1][y2]; if (!flg && w != -1) return w != 0; int num1 = num + 3 * (n - x) - 1; if (!check(mp[op][x][y1], num)) return w = 0; if (!check(mp[op][x][y2], num1)) return w = 0; if (y1 == 1) { if (!check(mp[op][x][2 - y2], num + 1)) return w = 0; return (w = (endline(op, x + 1, 2 - y2, y2, num + 2) ? 1 : 0)); } else { if (y2 == 1) { if (!check(mp[op][x][2 - y1], num1 - 1)) return w = 0; return (w = (endline(op, x + 1, y1, 2 - y1, num + 1) ? 1 : 0)); } else { // 两种可能的路径选择 if (w != 2) { if (check(mp[op][x][1], num + 1) && endline(op, x + 1, 1, y2, num + 2)) { w = 1; return 1; } } if (w != 1) { if (check(mp[op][x][1], num1 - 1) && endline(op, x + 1, y1, 1, num + 1)) { w = 2; return 1; } } } } return w = 0; } // 选择记录和哈密顿路径检查 pair<char, short> choice[2][1005][3][3]; char hamilton[2][1005][3][3]; // Z形路径检查 bool Zaw(int op, int x, int y, int yy, int len, int add) { if (add) { // 递增方向 int num = x * 3 + 1; int xx = x + len - 1; if (!judge(op, x, y, num, xx, add)) return 0; num = n * 3; num -= len - 1; if (!judge(op, x, 3 - y - yy, num, xx, add)) return 0; num--; if (!judge(op, x, yy, num, xx, 1 - add)) return 0; } else { // 递减方向 int num = (n - x) * 3; int xx = x + len - 1; if (!judge(op, x, y, num, xx, add)) return 0; num = 1; num += len - 1; if (!judge(op, x, 3 - y - yy, num, xx, add)) return 0; num++; if (!judge(op, x, yy, num, xx, 1 - add)) return 0; } return 1; } // 主求解函数 bool solve(int op, int x, int y, int add) { if (x == n) return 1; char &w = hamilton[op][x][y][add]; if (!flg && w != -1) return w; pair<char, short> &wo = choice[op][x][y][add]; // 尝试之字形路径 if (y != 1) { for (int i = x; i < n; i++) { if (wo.first != -1 && !(wo.first == 1 && wo.second == i)) continue; if (zigzag(op, x, y, i, add) && solve(op, i + 1, 2 - y, add)) { wo.first = 1; wo.second = i; return w = 1; } } } // 尝试端线模式 for (int yy = 0; yy <= 2; yy++) { if (yy == y) continue; if (wo.first != -1 && !(wo.first == 2 && wo.second == yy)) continue; int num; if (add) { num = 3 * x + 1; if (endline(op, x, y, yy, num)) { wo.first = 2; wo.second = yy; return w = 1; } } else { num = 1; if (endline(op, x, yy, y, num)) { wo.first = 2; wo.second = yy; return w = 1; } } } // 尝试混合模式 if (y != 1) { for (int len = 1; len + x < n; len++) { for (int yy = 0; yy <= 2; yy++) { if (yy == y) continue; if (wo.first != -1 && !(wo.first == 3 && wo.second / n == yy && wo.second % n == len)) continue; int num; if (add) num = 3 * x + 1; else num = 3 * (n - x); if (!Zaw(op, x, y, yy, len, add)) continue; if (add) num += len; else num -= len; int xx = x + len; if (add) { if (endline(op, xx, y, yy, num)) { wo.first = 3; wo.second = len + yy * n; return w = 1; } } else { num -= 3 * (n - xx) - 1; if (endline(op, xx, yy, y, num)) { wo.first = 3; wo.second = len + yy * n; return w = 1; } } } } } return w = 0; } // 输出结果 void Get() { for (int i = 0; i < n; i++) { for (int j = 0; j <= 2; j++) { check(mp[0][i][j], mp[1][n - i - 1][j]); } } for (int j = 0; j <= 2; j++) { for (int i = 0; i < n; i++) { printf("%d ", mp[0][i][j]); } puts(""); } } // 特殊端线模式处理 int ansop, ansl, ansr, ansy, ansyy; bool work(int op, int x, int xx, int y1, int y2, int y3) { int num = 1; if (!judge(op, x, y2, num, xx, 1)) return 0; num += 3 * (n - xx - 1) + 2 * (xx - x + 1) - 1; if (!judge(op, x, y1, num, xx, 0)) return 0; num += 3 * x + 1; if (!judge(op, x, y3, num, xx, 1)) return 0; return 1; } // 纯端线模式检查 bool onlyend() { for (int op = 0; op <= 1; op++) { for (int L = 0; L < n; L++) { for (int R = L; R < n; R++) { for (int y = 0; y <= 2; y++) { for (int yy = 0; yy <= 2; yy++) { if (y == yy) continue; if (flg && !(ansop == op && ansl == L && ansr == R && ansy == y && ansyy == yy)) continue; int yyy = 3 - y - yy; if (!work(op, L, R, y, yy, yyy)) continue; if (endline(op, R + 1, yy, y, R - L + 2) && endline(1 - op, n - L, y, yyy, (R - L + 1) * 2 + (n - R - 1) * 3 + 1)) { ansop = op, ansl = L, ansr = R, ansy = y, ansyy = yy; return 1; } } } } } } return 0; } int main() { n = read(); // 读入数据,同时存储正序和逆序 for (int j = 0; j <= 2; j++) { for (int i = 0; i < n; i++) { mp[1][n - i - 1][j] = mp[0][i][j] = read(); } } // 初始化状态数组 memset(endlin, -1, sizeof(endlin)); memset(line, -1, sizeof(line)); memset(choice, -1, sizeof(choice)); memset(hamilton, -1, sizeof(hamilton)); // 尝试所有可能的起始位置和方向 for (int i = 0; i <= n; i++) { for (int j = 0; j <= 2; j++) { for (int op = 0; op <= 1; op++) { bool flag = solve(0, i, j, op); bool flag1 = solve(1, n - i, j, 1 - op); if (flag & flag1) { flg = 1; // 进入构造阶段 solve(0, i, j, op); solve(1, n - i, j, 1 - op); Get(); // 输出结果 return 0; } } } } // 如果常规方法失败,尝试纯端线模式 if (onlyend()) { flg = 1; onlyend(); Get(); return 0; } }总结
算法特点
- 状态压缩:通过多维数组记录各种路径模式的状态
- 双向搜索:同时从网格两端搜索,提高效率
- 模式识别:识别蛇形路径的几种典型模式(之字形、端线、混合)
- 记忆化搜索:避免重复计算,提高性能
复杂度分析
- 时间复杂度:O(n²) 级别,由于有多个维度的状态,实际常数较大
- 空间复杂度:O(n²) 级别,使用了多个三维和四维数组
解题技巧
- 正反双向处理:同时维护正序和逆序的网格,便于双向验证
- 构造与验证分离:先验证可行性,再回溯构造解
- 多种路径模式:考虑蛇形路径的所有可能模式,确保不遗漏解
这道题综合运用了动态规划、搜索、构造等多种技巧,是一道比较有挑战性的题目。
- 1
信息
- ID
- 5085
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 3
- 已通过
- 1
- 上传者