原题入口
这个题 题意很明显 就是个求数独的题目。
- 常规做法:暴力枚举+判断可行性(会TLE)
- 进阶做法:一边算一边用数组标记(400多ms)
- 超神做法:用DLX算法(基本0ms)
这三种做法我都试过 一步步优化程度提升 最后DLX特别快 基本算普通的不需要时间 算骨灰级难度的也很快
所以我在这里介绍的就是DLX算法了 一开始不太明白 看了个大佬的程序 就基本上懂了 十分简洁明了 博客链接
但那个大佬的代码没啥注释 我就将她的代码进行略微改编,然后增加注释
我还是简单介绍下DLX算法吧(来自《算法竞赛入门经典-训练指南》):
这个算法是用于解决一些精准覆盖的问题 即有几个集合中包含一些元素 选取一些集合 使每个元素不重复且恰好出现一次。(可以运用到,N皇后,数独即一些特殊的问题上面去)
需要用一个01行列的表构造(以后有时间补上书上的说明)
很容易想得出是用搜索写法。 这里就是运用的搜索中的X算法: 就是选取还没有选过的一个元素,从包含它的元素集合中挑选,然后删除其他所有包含这个元素的集合。最后回溯时又复原这个集合。
为了更快的删除和恢复。所以我们就需要一个特殊的数据结构来维护,这里就出现了神奇的Dancing-Links(舞蹈链)。
这个数据结构记录了它上下左右最近的一个为1的存在节点,这个是运用的循环链表实现的,能够达到很快遍历该行和该列的目的。而且恢复也十分容易,只要修改相邻的节点就行了。 由于是循环列表,所以最后一列右边的节点就是第一列的节点,其他三个方面类推。
这个数独是怎么运用的后面去补吧。。。 最重要的是要分清本身的棋盘和舞蹈链组成的舞池的区别(因为都是矩阵 一开始不好区分)
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <cmath>
#include <algorithm>
#define Set(a, v) memset(a, v, sizeof(a))
#define For(i, l, r) for(int i = (l); i <= (int)(r); ++i)
#define Fordown(i, r, l) for(int i = (r); i >= (int)(l); --i)
#define Travel(i, A, s) for(int i = A[s]; i != s; i = A[i])
//之所以可以这样遍历 因为 这是个循环队列 可以遍历一列或者一行
using namespace std;
inline int read(){
int x = 0, fh = 1; char ch;
for(; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
for(; isdigit(ch); ch = getchar()) x = (x<<1) + (x<<3) + (ch^'0');
return x * fh;
}
const int maxr = 9*9*9 + 10;
const int maxc = 9*9*4 + 10;
const int inf = 0x3f3f3f3f;
int L[maxc+maxr*5], R[maxc+maxr*5], U[maxc+maxr*5], D[maxc+maxr*5]; //maxc+maxr*5是总共的最大节点数
//这边分别是四个指针,left,right,up,down上下左右 分别指向别的节点位置
int S[maxc]; //统计每列1的节点个数
int nRow[maxc+maxr*5], nCol[maxc+maxr*5]; //储存每个节点的行和列
int Head[10][10][10]; //储存每个位置 (数独中) 每个数字所对应的节点
int cnt = 0; //存储共有多少个节点
int G[10][10]; //存储答案图(和原来的图)
inline int sub_grid (int x, int y) {return ((x-1)/3)*3+((y-1)/3+1);} //把每个原数独中的节点所在宫的编号求出来
void insert (int c, int cnt) {
U[D[c]] = cnt;
D[cnt] = D[c];
U[cnt] = c;
D[c] = cnt;
++S[c];
nCol[cnt] = c;
} //插入一个节点
void remove (int c) {
L[R[c]] = L[c]; //右边的左边 变为这个节点的左边
R[L[c]] = R[c]; //右边的左边 变为这个节点的左边
//即删除这一列
Travel (i, D, c) //向下遍历
Travel (j, R, i) { //向右遍历
U[D[j]] = U[j]; //楼下的楼上变为楼上
D[U[j]] = D[j]; //楼下的楼上变为楼上
--S[nCol[j]]; //减少遍历行中含有1的列的所统计个数
} //删除包含这列有1的行
} //删除一列
void resume (int c) {
Travel (i, U, c)
Travel (j, L, i) {
U[D[j]] = D[U[j]] = j;
++S[nCol[j]];
}
L[R[c]] = R[L[c]] = c;
} //恢复一列
bool dfs (int d) {
if (d > 81) return true;
int c, minn = inf;
Travel (i, R, 0) {
if (!S[i]) return false;
if (S[i] < minn) {
minn = S[i];
c = i;
}
}
remove(c);
Travel(i, D, c) {
int tmp = nRow[i];
G[tmp/100][(tmp/10)%10] = tmp % 10;
Travel (j, R, i)
remove (nCol[j]);
if (dfs(d+1)) return true;
Travel (j, L, i)
resume(nCol[j]);
}
resume(c);
return false;
}
void init() {
For (i, 0, 81*4) {
S[i] = 0; //把每列的个数清零
U[i] = D[i] = i; //这个节点上下变为自己
L[i] = i-1; R[i] = i+1;
nCol[i] = 0; //
}
R[81*4] = 0; L[0] = 81*4;
cnt = 81*4;
For (i, 1, 9)
For (j, 1, 9) {
if (G[i][j]) { //有数字的情况
int k = G[i][j]; //取出这一位
For (u, 1, 4) {
L[cnt+u] = cnt + u - 1; //
R[cnt+u] = cnt + u + 1; //
nRow[cnt+u] = 100*i + 10*j + k; //把这行标号
}
L[cnt+1] = cnt+4; R[cnt+4] = cnt+1;
Head[i][j][k] = cnt + 1;
insert ((i-1) * 9 + j, cnt+1);
insert (81 + (i-1) * 9 + k, cnt+2);
insert (81*2 + (j-1) * 9 + k, cnt+3);
insert (81*3 + (sub_grid(i, j) - 1) * 9 + k, cnt+4);
cnt += 4;
}
else {
For (k, 1, 9) {
For (u, 1, 4) {
L[cnt+u] = cnt + u - 1;
R[cnt+u] = cnt + u + 1;
nRow[cnt+u] = 100*i + 10*j + k;
}
L[cnt+1] = cnt+4; R[cnt+4] = cnt+1;
Head[i][j][k] = cnt + 1;
insert ((i-1) * 9 + j, cnt+1);
insert (81 + (i-1) * 9 + k, cnt+2);
insert (81*2 + (j-1) * 9 + k, cnt+3);
insert (81*3 + (sub_grid(i, j) - 1) * 9 + k, cnt+4);
cnt += 4;
}
}
}
}
void solve () {
int k = 0;
For (i, 1, 9)
For (j, 1, 9)
if (G[i][j]) {
++k; int v = Head[i][j][G[i][j]];
remove (nCol[v] );
Travel(u, R, v)
remove (nCol[u]);
}
dfs(k+1);
For (i, 1, 9) {
For (j, 1, 9)
putchar (G[i][j] + '0');
putchar ('\n');
}
// putchar ('\n');
}
void input() {
Set(G, 0);
int cnt_x = 1, cnt_y = 0;
while (cnt_x != 9 || cnt_y != 9) {
char ch = getchar();
if (!isdigit(ch)) continue; //判断是否为数字 (特殊的读入方式)
++cnt_y;
if (cnt_y == 10) {
cnt_y = 1;
++cnt_x; //到这行结束 跳到下一行
}
G[cnt_x][cnt_y] = (ch ^ '0'); //把当前这个数独的位置记下来
}
}
int main (){
int t = read();
while (t--) {
input(); //输入
init(); //预处理
solve(); //解决问题
}
}
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】