题目:Sudoku
题意:求解数独。从样例和结果来看应该是简单难度的数独
思路:DFS
设置3个数组,row[i][j] 判断第i行是否放了j数字,col[i][j] 判断第i列是否放了j数字。square[i/3][j/3][x]判断第i/3行第j/3列个宫是否放置了x数字;
#include <iostream> #include <algorithm> #include <stdlib.h> #include <time.h> #include <cmath> #include <cstdio> #include <string> #include <cstring> #include <vector> #include <queue> #include <stack> #include <set> #define c_false ios_base::sync_with_stdio(false); cin.tie(0) #define INF 0x3f3f3f3f #define INFL 0x3f3f3f3f3f3f3f3f #define zero_(x,y) memset(x , y , sizeof(x)) #define zero(x) memset(x , 0 , sizeof(x)) #define MAX(x) memset(x , 0x3f ,sizeof(x)) #define swa(x,y) {LL s;s=x;x=y;y=s;} using namespace std ; #define N 50005 const double PI = acos(-1.0); typedef long long LL ; bool col[10][10], row[10][10], square[5][5][10]; char mapp[10]; int MAP[10][10]; int n; bool dfs(int z){ if(z>=81) return true; int x = z/9; int y = z%9; if(MAP[x][y]) return dfs(z+1); for(int i = 1; i<= 9; i++){ if(!row[x][i] && !col[y][i] && !square[x/3][y/3][i]){ MAP[x][y] = i; row[x][i] = col[y][i] = square[x/3][y/3][i] = 1; if(dfs(z+1)) return true; MAP[x][y] = 0; row[x][i] = col[y][i] = square[x/3][y/3][i] = 0; } } return false; } int main(){ //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); scanf("%d", &n); while(n--){ //memset(MAP, 0, sizeof(MAP)); memset(row, false, sizeof(row)); memset(col, false, sizeof(col)); memset(square, false, sizeof(square)); for(int i = 0; i<9; i++){ scanf("%s", mapp); for(int j = 0; j<9; j++){ MAP[i][j] = mapp[j] - '0'; int c = MAP[i][j]; if(MAP[i][j]) { row[i][c] = col[j][c] = square[i/3][j/3][c] = 1; } } } dfs(0); for(int i=0;i<9;i++){ for(int j=0;j<9;j++) printf("%d",MAP[i][j]); printf(" "); } } return 0; }
题目:poj 3074
思路:DLX是从数据结构角度优化01矩阵的精确覆盖和重复覆盖的一种数据结构;
它用十字链表只存储矩阵中的非0元,而01矩阵精确覆盖dfs过程中矩阵会越来越稀疏而且每次恢复现场会浪费大量时间,DLX同时解决了这两个问题。
本题关键在于将数独问题转化为01矩阵精确覆盖。
精确覆盖定义:
满足以下条件的集合为一个精确覆盖:
- S*中任意两个集合没有交集,即X中的元素在S*中出现最多一次
- S*中集合的全集为X,即X中的元素在S*中出现最少一次
合二为一,即X中的元素在S*中出现恰好一次。
举例:
令 = {N, O, E, P} 是集合X = {1, 2, 3, 4}的一个子集的集合,并满足:
- N = { }
- O = {1, 3}
- E = {2, 4}
- P = {2, 3}.
其中一个子集 {O, E} 是 X的一个精确覆盖,因为 O = {1, 3} 而 E = {2, 4} 的并集恰好是 X = {1, 2, 3, 4}。同理, {N, O, E} 也是 X.的一个精确覆盖。空集并不影响结论。
矩阵表示法:
包含关系可以用一个关系矩阵表示。. 矩阵每行表示S的一个子集,每列表示X中的一个元素。矩阵行列交点元素为1表示对应的元素在对应的集合中,不在则为0.
通过这种矩阵表示法,求一个精确覆盖转化为求矩阵的若干个行的集合,使每列有且仅有一个1。同时,该问题也是精确覆盖的典型例题之一。
下图为其中一个例子:
1 | 2 | 3 | 4 | 5 | 6 | 7 | |
A | 1 | 0 | 0 | 1 | 0 | 0 | 1 |
B | 1 | 0 | 0 | 1 | 0 | 0 | 0 |
C | 0 | 0 | 0 | 1 | 1 | 0 | 1 |
D | 0 | 0 | 1 | 0 | 1 | 1 | 0 |
E | 0 | 1 | 1 | 0 | 0 | 1 | 1 |
F | 0 | 1 | 0 | 0 | 0 | 0 | 1 |
我们可以发现B,D,F恰好保证每一列有且仅有一个1;
S* = {B, D, F} 便是一个精确覆盖。
DLX算法简述:
如果读取到一个格子是空的,那么加9行,分别表示这个格子填1到9这9个数字,如果读取到的格子是一个数字,那么就加一行就可以了,然后列有9*9*4列,前81列表示这一行表示填的是第i行第j列的格子,接下来81列表示第i行填写k,接下来81列表示第j列填写k,最后81列表示对应九宫格填写k。
步骤:
然后,就是正式的深搜了。在深搜的每一层:
1) 在十字链表表头中找出元素最少且非零的一列,将该列以及列中包含的所有行元素从十字链表中移除;
2) 枚举列中的每行(作为解的一部分),将与该行相交的所有其他列以及这些列包含的行元素在十字链表中移除,递归下一层深搜;
3) 递归返回时,需要将2)中移除的相应列和行添加回十字链表;
4) 所有行枚举完毕时,需要将1)中移除的相应列和行添加回十字链表。
注意,对链表进行增删操作时,需要以相反的顺序修改各结点。同时,也需要动态维护表头的元素数。
#include<iostream> #include<cstring> #include<string> #include<cstdio> #include<algorithm> #include<vector> #include<algorithm> using namespace std; // 列:(行+列+块)*9种可能+9*9个格子 // 行: 9*9*9 表示第i行第j列填k const int MAXN=(9+9+9)*9+9*9+9*9*9*9*9*4+10; #define INF 0xFFFFFF int size; int head,sz; int U[MAXN],D[MAXN],L[MAXN],R[MAXN]; int H[MAXN],ROW[MAXN],C[MAXN],S[MAXN],O[MAXN]; void remove(int c) { L[R[c]]=L[c]; R[L[c]]=R[c]; for(int i=D[c];i!=c;i=D[i]) { for(int j=R[i];j!=i;j=R[j]) { U[D[j]]=U[j]; D[U[j]]=D[j]; --S[C[j]]; } } } void resume(int c) { for(int i=U[c];i!=c;i=U[i]) { for(int j=L[i];j!=i;j=L[j]) { ++S[C[j]]; U[D[j]]=j; D[U[j]]=j; } } L[R[c]]=c; R[L[c]]=c; } bool dfs(int k) { if(R[head]==head) { sort(O,O+9*9); int p=0; for(int i=0;i<9;i++) { for(int j=0;j<9;j++) { int num=O[p++]; //cout<<num<<endl; num=num-(i*9+j)*9; printf("%d",num); } } printf(" "); return true; } int s=INF,c; for (int t=R[head];t!=head;t=R[t]) { if (S[t]<s) { s=S[t]; c=t; } } remove(c); for(int i=D[c];i!=c;i=D[i]) { O[k]=ROW[i]; for(int j=R[i];j!=i;j=R[j]) remove(C[j]); if(dfs(k+1)) return true; for(int j=L[i];j!=i;j=L[j]) resume(C[j]); } resume(c); return false; } void initDL(int n) { head=0; for(int i=0;i<=n;i++) { U[i]=i;D[i]=i; L[i]=i-1;R[i]=i+1; S[i]=0; } R[n]=0;L[0]=n;S[0]=INF+1; sz=n+1; memset(H,0,sizeof(H)); } void insert(int i, int j) { if(H[i]) { L[sz]=L[H[i]]; R[sz]=H[i]; L[R[sz]]=sz; R[L[sz]]=sz; } else { L[sz]=sz; R[sz]=sz; H[i]=sz; } U[sz]=U[j]; D[sz]=j; U[D[sz]]=sz; D[U[sz]]=sz; C[sz]=j; ROW[sz]=i; ++S[j]; ++sz; } char str[200]; void build() { int p=0; initDL(9*9*4); for(int i=0;i<9;i++) for(int j=1;j<=9;j++,p++) { int base=(i*9+j-1)*9; if(str[p]=='.') { for(int k=1;k<=9;k++) { int r; r=base+k; //第i行有数字k insert(r,i*9+k); //第j列有数字k insert(r,9*9+(j-1)*9+k); //第k块有数字k int block=(j-1)/3*3+i/3; insert(r,9*9*2+block*9+k); //第i行j列有一个数字(限制一个格子只填一个数) insert(r,9*9*3+i*9+j); } } else { int k=str[p]-'0'; int r=base+k; //第i行有数字k insert(r,i*9+k); //第j列有数字k insert(r,9*9+(j-1)*9+k); //第k块有数字k int block=(j-1)/3*3+i/3; insert(r,9*9*2+block*9+k); //第i行j列有一个数字(限制一个格子只填一个数) insert(r,9*9*3+i*9+j); } } } int main() { size=9; //9*9数独 while(~scanf("%s",str)) { if(strcmp(str,"end")==0) break; build(); dfs(0); } return 0; }